windows 进程线程

sky123

https://www.vergiliusproject.com/

KPCR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 0x3748 bytes (sizeof)
struct _KPCR
{
union
{
struct _NT_TIB NtTib; // 0x00: 用户模式线程信息(TEB 的一部分)
struct
{
struct _EXCEPTION_REGISTRATION_RECORD* Used_ExceptionList; // 0x00: 当前线程异常链表(与 fs:[0] 一致)
VOID* Used_StackBase; // 0x04: 当前线程栈顶指针
VOID* Spare2; // 0x08: 保留字段
VOID* TssCopy; // 0x0C: 当前处理器使用的 TSS(任务状态段)地址(调度中用于设置 esp0)
ULONG ContextSwitches; // 0x10: 当前处理器发生的上下文切换次数(调度计数)
ULONG SetMemberCopy; // 0x14: 当前 CPU 在处理器集合中的位图(如 PRCB SetMember)
VOID* Used_Self; // 0x18: 指向当前线程的 TEB(Thread Environment Block)
};
};

struct _KPCR* SelfPcr; // 0x1C: 自身 KPCR 地址,用于快速访问自身结构(fs:[1Ch])
struct _KPRCB* Prcb; // 0x20: 指向当前处理器的 PRCB(每核一个)
UCHAR Irql; // 0x24: 当前处理器的 IRQL(中断优先级级别)
ULONG IRR; // 0x28: 中断请求寄存器值(仅软件模拟)
ULONG IrrActive; // 0x2C: 当前激活的 IRR 位(软中断管理)
ULONG IDR; // 0x30: 中断分发寄存器(Interrupt Dispatch Register)
VOID* KdVersionBlock; // 0x34: 用于内核调试器的版本信息块
struct _KIDTENTRY* IDT; // 0x38: 中断描述符表(IDT)指针
struct _KGDTENTRY* GDT; // 0x3C: 全局描述符表(GDT)指针
struct _KTSS* TSS; // 0x40: TSS 指针(可能为历史保留字段,实际调度用 TssCopy)
USHORT MajorVersion; // 0x44: KPCR 主版本号
USHORT MinorVersion; // 0x46: KPCR 次版本号
ULONG SetMember; // 0x48: 当前处理器的位标识(如 1 << ProcessorNumber)
ULONG StallScaleFactor; // 0x4C: 延迟循环校准比例,用于时间延迟函数(KeStallExecutionProcessor)
UCHAR SpareUnused; // 0x50: 未使用(可能为对齐用途)
UCHAR Number; // 0x51: 当前处理器编号(Processor Number)
UCHAR Spare0; // 0x52: 保留
UCHAR SecondLevelCacheAssociativity; // 0x53: 二级缓存的相联度
ULONG VdmAlert; // 0x54: 虚拟 DOS 模式支持相关(V86 模式通知)
ULONG KernelReserved[14]; // 0x58: 内核保留字段(未公开使用)
ULONG SecondLevelCacheSize; // 0x90: 当前处理器二级缓存大小(单位:字节)
ULONG HalReserved[16]; // 0x94: HAL 保留字段(如 ACPI/APIC 中断管理)
ULONG InterruptMode; // 0xD4: 中断控制模式(边沿/电平等)
UCHAR Spare1; // 0xD8: 保留(对齐或系统保留)
ULONG KernelReserved2[17]; // 0xDC: 更多内核保留字段(结构对齐 + Future use)

struct _KPRCB PrcbData; // 0x120: 当前处理器的 PRCB 数据区(调度器核心状态)
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
struct _KPRCB {
USHORT MinorVersion; // 0x0: PRCB 的结构版本号低位,用于内核组件检查兼容性
USHORT MajorVersion; // 0x2: PRCB 的结构版本号高位,配合 MinorVersion 共同判断结构版本
struct _KTHREAD* CurrentThread; // 0x4: 当前处理器正在执行的线程,线程调度切换的来源
struct _KTHREAD* NextThread; // 0x8: 被计划切换到当前处理器的线程,参与调度选择
struct _KTHREAD* IdleThread; // 0xc: 当无其他线程可调度时绑定的空闲线程,用于节能或等待
UCHAR LegacyNumber; // 0x10: 逻辑处理器编号(旧逻辑),用于与历史多处理器调度逻辑兼容
UCHAR NestingLevel; // 0x11: 中断嵌套层级,嵌套越深表示处于更深层次中断或异常中
USHORT BuildType; // 0x12: 内核构建类型(如调试/零售版本)影响行为检查和断言启用
CHAR CpuType; // 0x14: 当前处理器的类型编码,用于平台差异判断
CHAR CpuID; // 0x15: 当前处理器的具体实现编号
union {
USHORT CpuStep; // 0x16: 汇总的 CPU 步进信息,含型号与修订
struct {
UCHAR CpuStepping; // 0x16: CPU 的修订版本(如 A0/B1 步进)
UCHAR CpuModel; // 0x17: CPU 的型号编号,区别不同微架构
};
};
struct _KPROCESSOR_STATE ProcessorState; // 0x18: 当前处理器完整的寄存器上下文,用于异常与上下文切换保存
ULONG KernelReserved[16]; // 0x338: 内核保留空间,供未来扩展或临时变量使用
ULONG HalReserved[16]; // 0x378: 硬件抽象层保留空间,与底层硬件配置和状态相关
ULONG CFlushSize; // 0x3b8: Cache 刷新操作的粒度大小(字节)
UCHAR CoresPerPhysicalProcessor; // 0x3bc: 每个物理处理器所包含的核心数,用于调度拓扑
UCHAR LogicalProcessorsPerCore; // 0x3bd: 每个核心上的逻辑处理器数量(如支持超线程时为 2)
UCHAR PrcbPad0[2]; // 0x3be: 对齐填充字节,无具体功能
ULONG MHz; // 0x3c0: 当前处理器运行频率(单位 MHz)
UCHAR CpuVendor; // 0x3c4: CPU 厂商标识(如 Intel、AMD)
UCHAR GroupIndex; // 0x3c5: 所属调度组的索引值
USHORT Group; // 0x3c6: 所在的处理器组编号(支持多组时使用)
ULONG GroupSetMember; // 0x3c8: 当前处理器在组内的成员掩码
ULONG Number; // 0x3cc: 当前 PRCB 的编号(即逻辑处理器号)
UCHAR PrcbPad1[72]; // 0x3d0: 对齐与保留空间
struct _KSPIN_LOCK_QUEUE LockQueue[17]; // 0x418: 自旋锁队列数组,表示多个锁的排队状态
struct _KTHREAD* NpxThread; // 0x4a0: 当前使用浮点/扩展寄存器状态的线程
ULONG InterruptCount; // 0x4a4: 当前处理器累计接收中断的次数
ULONG KernelTime; // 0x4a8: 当前线程在内核态所运行的时间累计
ULONG UserTime; // 0x4ac: 当前线程在用户态所运行的时间累计
ULONG DpcTime; // 0x4b0: DPC(延迟过程调用)占用的处理器时间
ULONG DpcTimeCount; // 0x4b4: DPC 调用次数计数
ULONG InterruptTime; // 0x4b8: 处理器用于处理中断的时间累计
ULONG AdjustDpcThreshold; // 0x4bc: 动态调整 DPC 的调度触发阈值,用于自适应优化
ULONG PageColor; // 0x4c0: 当前处理器页分配颜色值,影响缓存局部性
UCHAR DebuggerSavedIRQL; // 0x4c4: 调试器保存的中断优先级级别
UCHAR NodeColor; // 0x4c5: NUMA 节点着色标识,用于内存分配优化
UCHAR PrcbPad20[2]; // 0x4c6: 对齐用填充字节
ULONG NodeShiftedColor; // 0x4c8: 页颜色值向左移位后的结果(结合 NUMA 计算)
struct _KNODE* ParentNode; // 0x4cc: 指向所属 NUMA 节点的数据结构
ULONG SecondaryColorMask; // 0x4d0: 用于二级着色策略的掩码值(高级页分配策略)
ULONG DpcTimeLimit; // 0x4d4: 每次 DPC 执行的最大时间限制(单位 Tick)
ULONG PrcbPad21[2]; // 0x4d8: 保留填充字段
ULONG CcFastReadNoWait; // 0x4e0: Fast I/O 快速读取(无等待)命中的次数
ULONG CcFastReadWait; // 0x4e4: Fast I/O 快速读取发生等待后完成的次数
ULONG CcFastReadNotPossible; // 0x4e8: Fast I/O 快速路径不可用的尝试次数
ULONG CcCopyReadNoWait; // 0x4ec: CopyRead 操作中无等待直接完成的次数
ULONG CcCopyReadWait; // 0x4f0: CopyRead 操作等待后完成的次数
ULONG CcCopyReadNoWaitMiss; // 0x4f4: CopyRead 快速路径失败的次数(进入慢路径)
volatile LONG MmSpinLockOrdering; // 0x4f8: 内存管理中锁获取顺序调试字段,用于死锁检测
volatile LONG IoReadOperationCount; // 0x4fc: 当前处理器累计发出的 IO 读操作次数
volatile LONG IoWriteOperationCount; // 0x500: 当前处理器累计发出的 IO 写操作次数
volatile LONG IoOtherOperationCount; // 0x504: 其他类型 IO 操作(如设备控制)计数
union _LARGE_INTEGER IoReadTransferCount; // 0x508: IO 读请求累计传输的总字节数
union _LARGE_INTEGER IoWriteTransferCount; // 0x510: IO 写请求累计传输的总字节数
union _LARGE_INTEGER IoOtherTransferCount; // 0x518: IO 控制/分页等其他操作的传输总字节数
ULONG CcFastMdlReadNoWait; // 0x520: 使用 MDL 的快速读取操作(无等待)成功次数
ULONG CcFastMdlReadWait; // 0x524: 使用 MDL 的快速读取操作发生等待的次数
ULONG CcFastMdlReadNotPossible; // 0x528: 无法执行快速 MDL 读取的尝试次数
ULONG CcMapDataNoWait; // 0x52c: 映射数据页操作中无需等待即成功的次数
ULONG CcMapDataWait; // 0x530: 映射数据页操作发生等待后完成的次数
ULONG CcPinMappedDataCount; // 0x534: 被固定映射的数据页数量,防止被分页或清除
ULONG CcPinReadNoWait; // 0x538: 固定数据页后读取(无等待)成功的次数
ULONG CcPinReadWait; // 0x53c: 固定数据页后读取发生等待的次数
ULONG CcMdlReadNoWait; // 0x540: 使用 MDL 的无等待读取次数
ULONG CcMdlReadWait; // 0x544: 使用 MDL 的读取操作发生等待的次数
ULONG CcLazyWriteHotSpots; // 0x548: Lazy Writer 命中的热点页数量
ULONG CcLazyWriteIos; // 0x54c: Lazy Writer 触发的实际写入 IO 次数
ULONG CcLazyWritePages; // 0x550: Lazy Writer 写出的页数总计
ULONG CcDataFlushes; // 0x554: 数据缓存主动刷新次数(由系统调用等触发)
ULONG CcDataPages; // 0x558: 被刷新操作影响的数据页总数
ULONG CcLostDelayedWrites; // 0x55c: 丢失的延迟写记录次数(写回前被删除或失败)
ULONG CcFastReadResourceMiss; // 0x560: Fast I/O 快速读取因资源竞争(如锁被占用)而失败的次数
ULONG CcCopyReadWaitMiss; // 0x564: CopyRead 操作尝试快速路径但因等待失败进入慢路径的次数
ULONG CcFastMdlReadResourceMiss; // 0x568: 快速 MDL 读取因锁或资源冲突失败的尝试次数
ULONG CcMapDataNoWaitMiss; // 0x56c: 映射数据页的快速路径失败的次数(需等待资源)
ULONG CcMapDataWaitMiss; // 0x570: 映射数据页在等待后仍失败的次数(如被抢占)
ULONG CcPinReadNoWaitMiss; // 0x574: 固定页读取尝试无等待路径失败的次数
ULONG CcPinReadWaitMiss; // 0x578: 固定页读取在等待后仍失败的次数
ULONG CcMdlReadNoWaitMiss; // 0x57c: MDL 读取尝试快速路径失败的次数
ULONG CcMdlReadWaitMiss; // 0x580: MDL 读取在等待后仍然失败的次数
ULONG CcReadAheadIos; // 0x584: 后台预读操作产生的 IO 次数(提高性能)
ULONG KeAlignmentFixupCount; // 0x588: 因对齐错误而触发修复的异常次数(通常为非对齐内存访问)
ULONG KeExceptionDispatchCount; // 0x58c: 异常调度处理的次数(如访问冲突、除零)
ULONG KeSystemCalls; // 0x590: 当前处理器处理的系统调用总次数
ULONG AvailableTime; // 0x594: 当前处理器可用于调度的时间窗口(单位为 tick)
ULONG PrcbPad22[2]; // 0x598: 保留字段,对齐用
struct _PP_LOOKASIDE_LIST PPLookasideList[16]; // 0x5a0: 预分配非分页内存池 Lookaside 列表数组(提升分配效率)
struct _GENERAL_LOOKASIDE_POOL PPNPagedLookasideList[32]; // 0x620: 用于非分页内存池的小块分配优化结构
struct _GENERAL_LOOKASIDE_POOL PPPagedLookasideList[32]; // 0xf20: 用于分页内存池的小块分配优化结构
volatile ULONG PacketBarrier; // 0x1820: 同步屏障,确保处理器间的数据一致性操作按顺序生效
volatile LONG ReverseStall; // 0x1824: 调试/性能监控字段,用于反向暂停信号
VOID* IpiFrame; // 0x1828: IPI(中断)处理使用的帧缓存
UCHAR PrcbPad3[52]; // 0x182c: 填充保留字段
VOID* volatile CurrentPacket[3]; // 0x1860: 当前 IPI 通信处理的数据包(3 个参数)
volatile ULONG TargetSet; // 0x186c: IPI 的目标处理器集合(掩码)
VOID (* volatile WorkerRoutine)(VOID*, VOID*, VOID*, VOID*); // 0x1870: 用于执行调度工作项的通用函数指针
volatile ULONG IpiFrozen; // 0x1874: 当前处理器是否被冻结(用于停核或调试)
UCHAR PrcbPad4[40]; // 0x1878: 对齐与保留字段
volatile ULONG RequestSummary; // 0x18a0: 当前处理器接收的请求概要标志位(调度/DPC/异步操作)
struct _KPRCB* volatile SignalDone; // 0x18a4: IPI 通信完成信号指针,接收方写入确认
UCHAR PrcbPad50[56]; // 0x18a8: 填充空间,供扩展使用
struct _KDPC_DATA DpcData[2]; // 0x18e0: DPC 调度队列数据(Normal 和 Threaded 两类)
VOID* DpcStack; // 0x1908: 当前正在使用的 DPC 栈地址
LONG MaximumDpcQueueDepth; // 0x190c: DPC 队列允许的最大深度,超过后触发立即执行
ULONG DpcRequestRate; // 0x1910: DPC 请求速率统计值(单位时间内请求次数)
ULONG MinimumDpcRate; // 0x1914: 最小允许的 DPC 请求速率,低于此速率可降频
ULONG DpcLastCount; // 0x1918: 上次计数周期内处理的 DPC 数量
ULONG PrcbLock; // 0x191c: 用于保护 PRCB 内部数据结构的自旋锁
struct _KGATE DpcGate; // 0x1920: DPC 同步门,用于协调 DPC 激活与处理器休眠
UCHAR ThreadDpcEnable; // 0x1930: 指示当前线程是否允许执行 DPC(线程局部控制)
volatile UCHAR QuantumEnd; // 0x1931: 标识是否到达线程的时间片末尾(触发调度)
volatile UCHAR DpcRoutineActive; // 0x1932: 当前处理器是否正在执行 DPC(防止嵌套)
volatile UCHAR IdleSchedule; // 0x1933: 指示是否处于空闲线程调度状态
union {
volatile LONG DpcRequestSummary; // 0x1934: DPC 请求标志位(包括 Timer/DPC/Idle)
SHORT DpcRequestSlot[2]; // 拆分视图:表示不同类型的 DPC 请求
struct {
SHORT NormalDpcState; // 普通 DPC 状态标志
union {
volatile USHORT DpcThreadActive : 1; // 表示线程 DPC 是否处于活动状态
SHORT ThreadDpcState; // 线程 DPC 的完整状态位(包含是否运行/启用)
};
};
};
volatile ULONG TimerHand; // 0x1938: 当前处理器计时器扫描器的位置
ULONG LastTick; // 0x193c: 上一次系统 tick 的时间戳
LONG MasterOffset; // 0x1940: 在多处理器系统中记录主处理器的时间偏移
ULONG PrcbPad41[2]; // 0x1944: 填充字段
ULONG PeriodicCount; // 0x194c: 周期性计数器,用于触发周期任务(如调度)
ULONG PeriodicBias; // 0x1950: 周期性偏移值,用于负载均衡
ULONGLONG TickOffset; // 0x1958: 当前 tick 偏移值,用于时间轮精确推进
struct _KTIMER_TABLE TimerTable; // 0x1960: 当前处理器的内核计时器表(高效定时器轮)
struct _KDPC CallDpc; // 0x31a0: 用于通用函数调度的 DPC 对象
LONG ClockKeepAlive; // 0x31c0: 防止处理器休眠的保持计数(如调试、实时线程)
UCHAR ClockCheckSlot; // 0x31c4: 时钟检查槽位编号,用于统计校准
UCHAR ClockPollCycle; // 0x31c5: 用于控制时钟轮询周期(优化性能)
UCHAR PrcbPad6[2]; // 0x31c6: 填充字段
LONG DpcWatchdogPeriod; // 0x31c8: DPC 看门狗触发周期阈值(单位 tick)
LONG DpcWatchdogCount; // 0x31cc: 累计检测到 DPC 执行超时的次数
LONG ThreadWatchdogPeriod; // 0x31d0: 线程执行看门狗周期(用于检测卡死)
LONG ThreadWatchdogCount; // 0x31d4: 累计触发线程看门狗的次数
volatile LONG KeSpinLockOrdering; // 0x31d8: 内核锁顺序调试字段,用于检查死锁问题
ULONG PrcbPad70[1]; // 0x31dc: 填充空间
struct _LIST_ENTRY WaitListHead; // 0x31e0: 当前处理器就绪队列中的等待线程链表头
ULONG WaitLock; // 0x31e8: 用于保护 WaitList 的自旋锁
ULONG ReadySummary; // 0x31ec: 表示当前就绪队列中线程的优先级分布摘要
ULONG QueueIndex; // 0x31f0: 当前使用的就绪队列索引,用于负载均衡
struct _SINGLE_LIST_ENTRY DeferredReadyListHead; // 0x31f4: 延迟加入就绪队列的线程列表头(如 APC 延后切换)
ULONGLONG StartCycles; // 0x31f8: 当前线程启动以来的周期计数器值(用于 CPU 时间统计)
volatile ULONGLONG CycleTime; // 0x3200: 当前处理器累计运行的总时钟周期
volatile ULONG HighCycleTime; // 0x3208: CycleTime 的高位部分(便于 32 位系统支持)
ULONG PrcbPad71; // 0x320c: 填充字段
ULONGLONG PrcbPad72[2]; // 0x3210: 填充字段,可能用于对齐或未来扩展
struct _LIST_ENTRY DispatcherReadyListHead[32]; // 0x3220: 每个优先级对应的就绪线程队列链表头(共 32 个优先级)
VOID* ChainedInterruptList; // 0x3320: 链式中断处理函数链表,用于处理级联中断控制器
LONG LookasideIrpFloat; // 0x3324: Lookaside IRP 分配/释放之间的漂移值,用于调优
volatile LONG MmPageFaultCount; // 0x3328: 当前处理器处理的缺页异常次数(用于内存管理性能分析)
volatile LONG MmCopyOnWriteCount; // 0x332c: 拷贝写时异常(COW)触发次数
volatile LONG MmTransitionCount; // 0x3330: 内存页从无效状态过渡为有效的次数
volatile LONG MmCacheTransitionCount; // 0x3334: 缓存页状态转换次数(如从 standby 到 active)
volatile LONG MmDemandZeroCount; // 0x3338: 处理器上零页需求次数(如首次分配新页)
volatile LONG MmPageReadCount; // 0x333c: 处理器累计完成的页读取次数(从磁盘到内存)
volatile LONG MmPageReadIoCount; // 0x3340: 实际触发 IO 操作的页读取次数
volatile LONG MmCacheReadCount; // 0x3344: 从缓存中直接读取页的次数(无需 IO)
volatile LONG MmCacheIoCount; // 0x3348: 通过缓存路径触发 IO 的读取次数
volatile LONG MmDirtyPagesWriteCount; // 0x334c: 脏页写回磁盘的次数
volatile LONG MmDirtyWriteIoCount; // 0x3350: 实际 IO 层面写出脏页的次数
volatile LONG MmMappedPagesWriteCount; // 0x3354: 映射文件页写回的次数
volatile LONG MmMappedWriteIoCount; // 0x3358: 映射写触发的实际 IO 次数
volatile ULONG CachedCommit; // 0x335c: 当前已缓存但尚未提交的虚拟内存页数
volatile ULONG CachedResidentAvailable; // 0x3360: 当前可用于驻留页的缓存数量
VOID* HyperPte; // 0x3364: 用于 Hyper-V 下映射虚拟 PTE 页的指针
UCHAR PrcbPad8[4]; // 0x3368: 对齐保留字段
UCHAR VendorString[13]; // 0x336c: 处理器厂商名称字符串(如 "GenuineIntel")
UCHAR InitialApicId; // 0x3379: 本地 APIC 的初始 ID(用于中断路由)
UCHAR LogicalProcessorsPerPhysicalProcessor; // 0x337a: 每个物理处理器的逻辑处理器数(如超线程为 2)
UCHAR PrcbPad9[5]; // 0x337b: 填充保留字段
ULONG FeatureBits; // 0x3380: 当前处理器支持的特性位掩码(如 SSE、SMEP)
union _LARGE_INTEGER UpdateSignature; // 0x3388: 内核热补丁系统使用的更新签名
volatile ULONGLONG IsrTime; // 0x3390: 累计中断服务例程消耗的时间(周期数)
ULONGLONG RuntimeAccumulation; // 0x3398: 当前处理器所有线程运行总时间(tick 累积)
struct _PROCESSOR_POWER_STATE PowerState; // 0x33a0: 当前处理器的电源管理状态(如 C 状态、P 状态)
struct _KDPC DpcWatchdogDpc; // 0x3468: 用于检测 DPC 超时的 DPC 对象
struct _KTIMER DpcWatchdogTimer; // 0x3488: DPC 看门狗定时器,对应定期检查是否卡住
VOID* WheaInfo; // 0x34b0: 指向 WHEA(硬件错误体系)上下文信息
VOID* EtwSupport; // 0x34b4: ETW(事件跟踪)支持结构,用于调试/性能分析
union _SLIST_HEADER InterruptObjectPool; // 0x34b8: 中断对象池,用于快速分配中断处理结构
union _SLIST_HEADER HypercallPageList; // 0x34c0: Hyper-V 下的 Hypercall 页链表
VOID* HypercallPageVirtual; // 0x34c8: Hypercall 虚拟页地址(供 Hyper-V 使用)
VOID* VirtualApicAssist; // 0x34cc: 虚拟化辅助结构,用于管理 APIC 虚拟行为
ULONGLONG* StatisticsPage; // 0x34d0: 指向当前处理器的统计信息页(调试/性能用)
VOID* RateControl; // 0x34d4: 用于调节处理器性能状态的控制结构
struct _CACHE_DESCRIPTOR Cache[5]; // 0x34d8: 当前处理器的缓存层级描述(L1/L2/L3)
ULONG CacheCount; // 0x3514: 实际有效的 Cache 描述符数量
ULONG CacheProcessorMask[5]; // 0x3518: 每个缓存层级对应的处理器掩码(关联核心)
struct _KAFFINITY_EX PackageProcessorSet; // 0x352c: 当前处理器所在处理器包的亲和掩码
ULONG PrcbPad91[1]; // 0x3538: 填充字段
ULONG CoreProcessorSet; // 0x353c: 表示所属核心组内的处理器掩码
struct _KDPC TimerExpirationDpc; // 0x3540: 用于处理计时器过期的 DPC 回调
ULONG SpinLockAcquireCount; // 0x3560: 当前处理器尝试获取自旋锁的次数
ULONG SpinLockContentionCount; // 0x3564: 发生锁竞争(失败)后重试的次数
ULONG SpinLockSpinCount; // 0x3568: 自旋次数总计(衡量锁效率)
ULONG IpiSendRequestBroadcastCount; // 0x356c: 广播 IPI 请求发送次数
ULONG IpiSendRequestRoutineCount; // 0x3570: 单播 IPI 请求(例程)发送次数
ULONG IpiSendSoftwareInterruptCount; // 0x3574: 软件中断(软 IPI)发送次数
ULONG ExInitializeResourceCount; // 0x3578: 初始化资源锁的次数(如 ERESOURCE)
ULONG ExReInitializeResourceCount; // 0x357c: 重新初始化资源锁的次数
ULONG ExDeleteResourceCount; // 0x3580: 删除资源锁的次数
ULONG ExecutiveResourceAcquiresCount; // 0x3584: 成功获取 ERESOURCE 锁(独占/共享)的总次数
ULONG ExecutiveResourceContentionsCount; // 0x3588: 获取 ERESOURCE 锁时发生竞争的次数
ULONG ExecutiveResourceReleaseExclusiveCount; // 0x358c: 释放独占资源锁的次数
ULONG ExecutiveResourceReleaseSharedCount; // 0x3590: 释放共享资源锁的次数
ULONG ExecutiveResourceConvertsCount; // 0x3594: 将资源锁从共享转换为独占的次数
ULONG ExAcqResExclusiveAttempts; // 0x3598: 尝试获取独占资源锁的总次数
ULONG ExAcqResExclusiveAcquiresExclusive; // 0x359c: 成功独占获取资源锁的次数
ULONG ExAcqResExclusiveAcquiresExclusiveRecursive; // 0x35a0: 递归独占获取资源锁的次数
ULONG ExAcqResExclusiveWaits; // 0x35a4: 独占获取资源锁发生等待的次数
ULONG ExAcqResExclusiveNotAcquires; // 0x35a8: 获取独占锁失败的次数
ULONG ExAcqResSharedAttempts; // 0x35ac: 尝试获取共享锁的次数
ULONG ExAcqResSharedAcquiresExclusive; // 0x35b0: 共享获取请求被提升为独占的次数
ULONG ExAcqResSharedAcquiresShared; // 0x35b4: 成功获取共享资源锁的次数
ULONG ExAcqResSharedAcquiresSharedRecursive; // 0x35b8: 递归共享获取资源锁的次数
ULONG ExAcqResSharedWaits; // 0x35bc: 获取共享锁发生等待的次数
ULONG ExAcqResSharedNotAcquires; // 0x35c0: 获取共享锁失败的次数
ULONG ExAcqResSharedStarveExclusiveAttempts; // 0x35c4: 共享锁试图饥饿独占锁的尝试次数
ULONG ExAcqResSharedStarveExclusiveAcquiresExclusive; // 0x35c8: 在饥饿独占状态下成功获取独占锁的次数
ULONG ExAcqResSharedStarveExclusiveAcquiresShared; // 0x35cc: 饥饿独占时成功获取共享锁的次数
ULONG ExAcqResSharedStarveExclusiveAcquiresSharedRecursive; // 0x35d0: 饥饿独占中递归共享获取的次数
ULONG ExAcqResSharedStarveExclusiveWaits; // 0x35d4: 饥饿独占获取资源锁等待的次数
ULONG ExAcqResSharedStarveExclusiveNotAcquires; // 0x35d8: 饥饿独占获取资源锁失败的次数
ULONG ExAcqResSharedWaitForExclusiveAttempts; // 0x35dc: 等待获取独占资源锁的尝试次数
ULONG ExAcqResSharedWaitForExclusiveAcquiresExclusive; // 0x35e0: 成功等待并获取独占资源锁的次数
ULONG ExAcqResSharedWaitForExclusiveAcquiresShared; // 0x35e4: 成功等待并获取共享资源锁的次数
ULONG ExAcqResSharedWaitForExclusiveAcquiresSharedRecursive; // 0x35e8: 递归等待获取共享锁的次数
ULONG ExAcqResSharedWaitForExclusiveWaits; // 0x35ec: 等待独占锁发生实际等待的次数
ULONG ExAcqResSharedWaitForExclusiveNotAcquires; // 0x35f0: 等待独占锁但最终未获得的次数
ULONG ExSetResOwnerPointerExclusive; // 0x35f4: 设置资源锁独占拥有者的次数
ULONG ExSetResOwnerPointerSharedNew; // 0x35f8: 设置新的共享锁拥有者的次数
ULONG ExSetResOwnerPointerSharedOld; // 0x35fc: 替换旧共享锁拥有者的次数
ULONG ExTryToAcqExclusiveAttempts; // 0x3600: 尝试无等待方式获取独占锁的次数
ULONG ExTryToAcqExclusiveAcquires; // 0x3604: 成功获取的无等待独占锁次数
ULONG ExBoostExclusiveOwner; // 0x3608: 提升独占锁持有线程优先级的次数
ULONG ExBoostSharedOwners; // 0x360c: 提升共享锁持有线程优先级的次数
ULONG ExEtwSynchTrackingNotificationsCount; // 0x3610: ETW 同步跟踪通知的总次数
ULONG ExEtwSynchTrackingNotificationsAccountedCount; // 0x3614: 实际被记录统计的 ETW 跟踪通知次数
struct _CONTEXT* Context; // 0x3618: 指向当前线程的上下文信息结构(调试或中断使用)
ULONG ContextFlags; // 0x361c: 当前上下文结构的标志位(控制保存的内容)
struct _XSAVE_AREA* ExtendedState; // 0x3620: 扩展寄存器(如 AVX)状态保存区域
};

进程相关

进程结构

Windows 进程的完整描述确实涉及 KPROCESSEPROCESSPEB 三个结构,它们分别代表:

  • KPROCESS:进程的内核调度上下文(最底层、最精简)
  • EPROCESS:内核中代表进程的主对象,包含完整管理信息(含 KPROCESS
  • PEB:用户态结构,向 ntdll 和应用程序提供运行环境(仅用户可见)

提示

  • 由于 KPROCESSEPROCESS 的第一个成员 Pcb,因此 KPROCESSEPROCESS 的地址是相同的。

  • WDK 中导入的一个全局变量 PsInitialSystemProcess 指向系统启动后创建的第一个进程(PID 4),即 System 进程。

    1
    extern NTKERNELAPI PEPROCESS PsInitialSystemProcess;
  • 我们可以通过 PsGetCurrentProcess 可以获取到当前线程的 KPROCESS 地址,该函数实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    PsGetCurrentProcess proc near
    mov eax, fs:[KPCR.PrcbData.CurrentThread]
    ; eax ← 当前处理器上的当前线程指针 (_KTHREAD*)

    mov eax, [eax+_KTHREAD.ApcState.Process]
    ; eax ← 当前线程所隶属的进程指针 (_EPROCESS*)

    retn
    ; 返回 eax(即 _EPROCESS*)

    PsGetCurrentProcess endp
  • 通过 PsGetProcessPeb 可以获取到用户态的 PEB 的地址。

    1
    2
    3
    4
    PPEB PsGetProcessPeb(__in PEPROCESS Process)
    {
    return Process->Peb;
    }

KPROCESS

KPROCESS 调度器层面的“进程调度结构体”,轻量,仅描述 CPU 调度相关属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 0x98 bytes (sizeof)
struct _KPROCESS
{
struct _DISPATCHER_HEADER Header; // 0x00: 内核对象头,支持调度器的等待/信号机制(继承自调度器对象)
struct _LIST_ENTRY ProfileListHead; // 0x10: 性能分析器使用的链表,用于跟踪采样信息
ULONG DirectoryTableBase; // 0x18: 📌页目录基址(CR3),用于进程虚拟地址空间的转换
struct _KGDTENTRY LdtDescriptor; // 0x1C: 进程的 LDT(本地描述符表)描述符(32位兼容)
struct _KIDTENTRY Int21Descriptor; // 0x24: 兼容 INT 21H(DOS 中断)的描述符
struct _LIST_ENTRY ThreadListHead; // 0x2C: 📌本进程包含的所有线程链表头(每个 ETHREAD 挂在上面)
ULONG ProcessLock; // 0x34: 内部同步锁,用于进程结构保护
struct _KAFFINITY_EX Affinity; // 0x38: 允许在哪些处理器组(NUMA node)上运行的亲和性掩码
struct _LIST_ENTRY ReadyListHead; // 0x44: 当前进程就绪线程列表(调度器使用)
struct _SINGLE_LIST_ENTRY SwapListEntry; // 0x4C: 换出链表,用于支持进程的虚拟内存分页
volatile struct _KAFFINITY_EX ActiveProcessors;// 0x50: 当前活跃运行该进程线程的处理器掩码

union
{
struct
{
volatile LONG AutoAlignment:1; // 0x5C.0: 自动对齐模式(用于捕获未对齐访问)
volatile LONG DisableBoost:1; // 0x5C.1: 禁用线程优先级提升(防止因等待而提升优先级)
volatile LONG DisableQuantum:1; // 0x5C.2: 禁用时间片限制(不被抢占)
volatile ULONG ActiveGroupsMask:1; // 0x5C.3: 活跃处理器组标志
volatile LONG ReservedFlags:28; // 0x5C.4~31: 保留
};
volatile LONG ProcessFlags; // 0x5C: 原始标志位表示,整体视图
};

CHAR BasePriority; // 0x60: 初始线程优先级(线程继承此值)
CHAR QuantumReset; // 0x61: 时间片重置值(用于线程运行时刷新)
UCHAR Visited; // 0x62: NUMA 节点访问标志(内部使用)
UCHAR Unused3; // 0x63: 保留

ULONG ThreadSeed[1]; // 0x64: 用于线程亲和性随机调度的种子
USHORT IdealNode[1]; // 0x68: 线程理想的 NUMA 节点
USHORT IdealGlobalNode; // 0x6A: 全局理想节点编号

union _KEXECUTE_OPTIONS Flags; // 0x6C: 执行选项(禁止动态代码等安全配置)
UCHAR Unused1; // 0x6D: 保留
USHORT IopmOffset; // 0x6E: IO 权限位图偏移(用于 Virtual 8086 模式 I/O 控制)

ULONG Unused4; // 0x70: 保留

union _KSTACK_COUNT StackCount; // 0x74: 栈引用计数(线程栈使用情况)

struct _LIST_ENTRY ProcessListEntry; // 0x78: 调度器用的进程链表项(可能链接所有活跃进程)

volatile ULONGLONG CycleTime; // 0x80: 此进程被调度的 CPU 时间(时钟周期)
ULONG KernelTime; // 0x88: 内核模式运行时间(单位:100ns)
ULONG UserTime; // 0x8C: 用户模式运行时间(单位:100ns)

VOID* VdmTrapcHandler; // 0x90: 虚拟 DOS 兼容处理程序(V86 模式支持)
};

EPROCESS

EPROCESS 是 Windows 内核中最核心的进程对象结构,管理进程生命周期、对象句柄、内存空间等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
struct _EPROCESS
{
struct _KPROCESS Pcb; // 0x00: 📌进程调度器相关信息(KPROCESS,前面已详细注释)
struct _EX_PUSH_LOCK ProcessLock; // 0x98: 进程结构体自旋锁(用于多核同步)
union _LARGE_INTEGER CreateTime; // 0xA0: 进程创建时间
union _LARGE_INTEGER ExitTime; // 0xA8: 进程退出时间(如果还在运行则为 0)
struct _EX_RUNDOWN_REF RundownProtect; // 0xB0: 清理保护机制(防止进程在使用中被删除)
VOID* UniqueProcessId; // 0xB4: 📌当前进程的唯一 PID(HANDLE 类型)
struct _LIST_ENTRY ActiveProcessLinks; // 0xB8: 📌所有活动进程的双向链表
ULONG ProcessQuotaUsage[2]; // 0xC0: 当前使用的分页池和非分页池配额
ULONG ProcessQuotaPeak[2]; // 0xC8: 使用分页池和非分页池配额的历史峰值
volatile ULONG CommitCharge; // 0xD0: 当前已提交虚拟内存(以页为单位)
struct _EPROCESS_QUOTA_BLOCK* QuotaBlock; // 0xD4: 引用进程所属的配额块
struct _PS_CPU_QUOTA_BLOCK* CpuQuotaBlock; // 0xD8: CPU 使用配额控制结构
ULONG PeakVirtualSize; // 0xDC: 虚拟内存使用峰值
ULONG VirtualSize; // 0xE0: 当前虚拟内存使用量
struct _LIST_ENTRY SessionProcessLinks; // 0xE4: 当前进程所属 session 的进程链表
VOID* DebugPort; // 0xEC: 📌调试器端口(被调试时非 NULL)
union
{
VOID* ExceptionPortData; // 0xF0: 异常端口数据(调试器使用)
ULONG ExceptionPortValue;
ULONG ExceptionPortState:3; // 异常端口状态位
};
struct _HANDLE_TABLE* ObjectTable; // 0xF4: 📌当前进程句柄表(用于对象管理)
struct _EX_FAST_REF Token; // 0xF8: 📌进程访问令牌(权限、安全上下文)
ULONG WorkingSetPage; // 0xFC: 工作集起始页(即最小驻留页数)
struct _EX_PUSH_LOCK AddressCreationLock; // 0x100: 地址空间创建时使用的锁
struct _ETHREAD* RotateInProgress; // 0x104: 正在切换线程(调度使用)
struct _ETHREAD* ForkInProgress; // 0x108: 正在进行 fork 操作的线程
ULONG HardwareTrigger; // 0x10C: 硬件相关标志,可能由调试或诊断工具触发
struct _MM_AVL_TABLE* PhysicalVadRoot; // 0x110: VAD AVL 树根指针(描述虚拟内存分布)
VOID* CloneRoot; // 0x114: 克隆 VAD 树根(用于进程克隆)
volatile ULONG NumberOfPrivatePages; // 0x118: 分配给进程的私有页数
volatile ULONG NumberOfLockedPages; // 0x11C: 被锁定(不可换出)的页数
VOID* Win32Process; // 0x120: 指向 Win32 子系统使用的进程结构
struct _EJOB* volatile Job; // 0x124: 所属作业对象(Job 对象)
VOID* SectionObject; // 0x128: 映像节对象(表示映像在内存中的映射)
VOID* SectionBaseAddress; // 0x12C: 映像加载的基址(即 EXE 的加载地址)
ULONG Cookie; // 0x130: 安全 cookie(用于防护堆栈溢出等)
ULONG Spare8; // 0x134: 保留字段
struct _PAGEFAULT_HISTORY* WorkingSetWatch; // 0x138: 页面错误历史记录(用于诊断内存访问)
VOID* Win32WindowStation; // 0x13C: 进程关联的窗口站
VOID* InheritedFromUniqueProcessId; // 0x140: 父进程 PID(如果有)
VOID* LdtInformation; // 0x144: 本地描述符表(LDT)信息(32位支持)
VOID* VdmObjects; // 0x148: 虚拟 DOS 支持结构体(V86 模式)
ULONG ConsoleHostProcess; // 0x14C: 控制台宿主进程 PID(仅控制台进程使用)
VOID* DeviceMap; // 0x150: 设备映射信息(如 \\Device\\HarddiskX)
VOID* EtwDataSource; // 0x154: ETW 跟踪数据源指针
VOID* FreeTebHint; // 0x158: 用于优化 TEB 分配的 hint 指针
union
{
struct _HARDWARE_PTE PageDirectoryPte; // 0x160: 页目录表条目(硬件 PTE 格式)
ULONGLONG Filler;
};
VOID* Session; // 0x168: 当前进程所属的 session 对象
UCHAR ImageFileName[15]; // 0x16C: 📌可执行文件名称(不带路径)
UCHAR PriorityClass; // 0x17B: 优先级类别(与线程优先级相关)
struct _LIST_ENTRY JobLinks; // 0x17C: Job 对象下进程的链表节点
VOID* LockedPagesList; // 0x184: 锁定页链表
struct _LIST_ENTRY ThreadListHead; // 0x188: 当前进程线程链表头
VOID* SecurityPort; // 0x190: 安全通信端口(IPC)
VOID* PaeTop; // 0x194: PAE 页表顶端指针(仅在 PAE 模式使用)
volatile ULONG ActiveThreads; // 0x198: 当前活跃线程数量
ULONG ImagePathHash; // 0x19C: 映像路径哈希值(用于快速查找)
ULONG DefaultHardErrorProcessing; // 0x1A0: 错误处理策略(如是否弹框)
LONG LastThreadExitStatus; // 0x1A4: 最后一个线程的退出码
struct _PEB* Peb; // 0x1A8: 📌指向用户态的 PEB 结构
struct _EX_FAST_REF PrefetchTrace; // 0x1AC: 预取跟踪结构
union _LARGE_INTEGER ReadOperationCount; // 0x1B0: 读操作次数
union _LARGE_INTEGER WriteOperationCount; // 0x1B8: 写操作次数
union _LARGE_INTEGER OtherOperationCount; // 0x1C0: 其他操作次数
union _LARGE_INTEGER ReadTransferCount; // 0x1C8: 读取字节数总和
union _LARGE_INTEGER WriteTransferCount; // 0x1D0: 写入字节数总和
union _LARGE_INTEGER OtherTransferCount; // 0x1D8: 其他传输字节数
ULONG CommitChargeLimit; // 0x1E0: 提交内存配额上限
volatile ULONG CommitChargePeak; // 0x1E4: 提交内存配额峰值
VOID* AweInfo; // 0x1E8: Address Windowing Extensions 支持
struct _SE_AUDIT_PROCESS_CREATION_INFO SeAuditProcessCreationInfo; // 0x1EC: 📌审计信息,指向映像路径(可能包含命令行、EXE 路径)的结构体,供审计系统使用
struct _MMSUPPORT Vm; // 0x1F0: 进程的内存管理器支持结构(MMSUPPORT)
struct _LIST_ENTRY MmProcessLinks; // 0x25C: 用于分页内存管理的进程链表
VOID* HighestUserAddress; // 0x264: 用户态可访问的最高地址
ULONG ModifiedPageCount; // 0x268: 被修改的页面计数
union
{
ULONG Flags2; // 0x26C: 与调度、NUMA、安全、资源管理有关的扩展状态标志
struct
{
ULONG JobNotReallyActive:1; // bit 0: 进程关联的 Job 对象当前未被视为活跃状态
ULONG AccountingFolded:1; // bit 1: 资源使用计数合并入 Job,不独立统计进程自身
ULONG NewProcessReported:1; // bit 2: 进程创建事件已上报(防止重复报告)
ULONG ExitProcessReported:1; // bit 3: 进程退出事件已上报
ULONG ReportCommitChanges:1; // bit 4: 提交内存变化需要通知资源跟踪
ULONG LastReportMemory:1; // bit 5: 记录上次内存报告(用于比较/优化)
ULONG ReportPhysicalPageChanges:1; // bit 6: 页面物理位置变化需上报(诊断用途)
ULONG HandleTableRundown:1; // bit 7: 句柄表正在清理中
ULONG NeedsHandleRundown:1; // bit 8: 需要清理句柄表
ULONG RefTraceEnabled:1; // bit 9: 启用引用计数跟踪
ULONG NumaAware:1; // bit10: 该进程为 NUMA 感知进程
ULONG ProtectedProcess:1; // bit11: 📌受保护进程(如 lsass,不可注入/调试)
ULONG DefaultPagePriority:3; // bit12–14: 默认页面优先级(用于分页决策)
ULONG PrimaryTokenFrozen:1; // bit15: 主令牌被冻结(不可替换)
ULONG ProcessVerifierTarget:1; // bit16: 启用进程验证器跟踪
ULONG StackRandomizationDisabled:1; // bit17: 禁用堆栈地址随机化(如调试中)
ULONG AffinityPermanent:1; // bit18: 永久绑定 CPU 亲和性
ULONG AffinityUpdateEnable:1; // bit19: 允许动态更新亲和性
ULONG PropagateNode:1; // bit20: 跨 NUMA 节点传播数据
ULONG ExplicitAffinity:1; // bit21: 已显式设置亲和性
};
};

union
{
ULONG Flags; // 0x270: 控制进程生命周期、内存行为、调试行为等的主状态标志
struct
{
ULONG CreateReported:1; // bit 0: 已报告进程创建事件(如 ETW)
ULONG NoDebugInherit:1; // bit 1: 禁止继承调试器(子进程隔离)
ULONG ProcessExiting:1; // bit 2: 进程正在退出(终止流程中)
ULONG ProcessDelete:1; // bit 3: 进程对象标记为删除
ULONG Wow64SplitPages:1; // bit 4: 启用分页拆分机制(32位兼容性)
ULONG VmDeleted:1; // bit 5: 虚拟内存空间已销毁
ULONG OutswapEnabled:1; // bit 6: 允许被换出内存(Outswap)
ULONG Outswapped:1; // bit 7: 已被换出内存
ULONG ForkFailed:1; // bit 8: Fork 操作失败
ULONG Wow64VaSpace4Gb:1; // bit 9: 启用 Wow64 的完整 4GB 虚拟地址空间
ULONG AddressSpaceInitialized:2; // bit10–11: 地址空间初始化状态(0=未初始化,1=中,2=完成)
ULONG SetTimerResolution:1; // bit12: 请求高分辨率定时器
ULONG BreakOnTermination:1; // bit13: 📌终止进程时触发调试器中断
ULONG DeprioritizeViews:1; // bit14: 降低视图优先级
ULONG WriteWatch:1; // bit15: 启用写入监控页机制
ULONG ProcessInSession:1; // bit16: 进程属于用户会话
ULONG OverrideAddressSpace:1; // bit17: 允许覆盖地址空间
ULONG HasAddressSpace:1; // bit18: 地址空间已分配
ULONG LaunchPrefetched:1; // bit19: 由预取器发起的进程
ULONG InjectInpageErrors:1; // bit20: 启用页错误注入
ULONG VmTopDown:1; // bit21: 自顶向下分配地址空间
ULONG ImageNotifyDone:1; // bit22: 映像加载通知已完成
ULONG PdeUpdateNeeded:1; // bit23: 需要更新页目录项
ULONG VdmAllowed:1; // bit24: 允许 V86 模式执行(虚拟 8086)
ULONG CrossSessionCreate:1; // bit25: 允许跨会话创建
ULONG ProcessInserted:1; // bit26: 📌进程已插入系统链表
ULONG DefaultIoPriority:3; // bit27–29: 默认 I/O 优先级(0=低优先,3=普通)
ULONG ProcessSelfDelete:1; // bit30: 允许进程自删除(常用于恶意进程清理)
ULONG SetTimerResolutionLink:1; // bit31: 关联定时器精度链表
};
};
LONG ExitStatus; // 0x274: 📌进程的退出码,259(0x103)是 Windows 的“还在运行”的默认占位退出码,可以借此判断进程是否退出
struct _MM_AVL_TABLE VadRoot; // 0x278: 📌VAD 树(内存分配记录 AVL 树)
struct _ALPC_PROCESS_CONTEXT AlpcContext; // 0x298: ALPC 本地进程通信上下文
struct _LIST_ENTRY TimerResolutionLink; // 0x2A8: 定时器精度链接节点
ULONG RequestedTimerResolution; // 0x2B0: 请求的定时器精度
ULONG ActiveThreadsHighWatermark; // 0x2B4: 活跃线程历史最高值
ULONG SmallestTimerResolution; // 0x2B8: 系统允许的最小定时器分辨率
struct _PO_DIAG_STACK_RECORD* TimerResolutionStackRecord; // 0x2BC: 用于记录调用栈等定时器信息
};

PEB

PEB 仅存在于用户模式,描述当前进程的用户态环境,如模块、堆、TLS、参数等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
struct _PEB
{
UCHAR InheritedAddressSpace; // 0x00: 是否继承地址空间(fork 子进程时为 TRUE)
UCHAR ReadImageFileExecOptions; // 0x01: 是否读取镜像执行选项(调试器等兼容选项)
UCHAR BeingDebugged; // 0x02: 当前进程是否正在被调试(由调试器设置)
union
{
UCHAR BitField; // 0x03: 标志位集合
struct
{
UCHAR ImageUsesLargePages:1; // bit 0: 镜像是否使用大页内存
UCHAR IsProtectedProcess:1; // bit 1: 是否为受保护进程(如 lsass)
UCHAR IsLegacyProcess:1; // bit 2: 是否为传统进程(兼容旧系统)
UCHAR IsImageDynamicallyRelocated:1; // bit 3: 是否启用了 ASLR(地址随机化)
UCHAR SkipPatchingUser32Forwarders:1; // bit 4: 是否跳过 User32.dll 的 API 前向导出修补
UCHAR SpareBits:3; // bit 5-7: 保留位
};
};
VOID* Mutant; // 0x04: 用于同步的互斥体句柄(防止并发创建)
VOID* ImageBaseAddress; // 0x08: 主模块(EXE)加载基地址
struct _PEB_LDR_DATA* Ldr; // 0x0C: 指向模块加载器(LDR)数据结构
struct _RTL_USER_PROCESS_PARAMETERS* ProcessParameters; // 0x10: 进程参数,如命令行、环境变量等
VOID* SubSystemData; // 0x14: 子系统专用数据(如 POSIX 子系统)
VOID* ProcessHeap; // 0x18: 默认进程堆的地址
struct _RTL_CRITICAL_SECTION* FastPebLock; // 0x1C: 用于快速同步的临界区锁
VOID* AtlThunkSListPtr; // 0x20: ATL thunk 使用的单链表指针
VOID* IFEOKey; // 0x24: 映像文件执行选项(Image File Execution Options)注册表键句柄

union
{
ULONG CrossProcessFlags; // 0x28: 进程状态标志
struct
{
ULONG ProcessInJob:1; // bit 0: 进程是否属于 Job 对象
ULONG ProcessInitializing:1; // bit 1: 进程是否尚在初始化中
ULONG ProcessUsingVEH:1; // bit 2: 是否使用 VEH 异常处理(Vectored Exception Handler)
ULONG ProcessUsingVCH:1; // bit 3: 是否使用 VCH 异常处理(Vectored Continue Handler)
ULONG ProcessUsingFTH:1; // bit 4: 是否启用了故障容错处理(Fault Tolerant Heap)
ULONG ReservedBits0:27; // bit 5-31: 保留
};
};

union
{
VOID* KernelCallbackTable; // 0x2C: 内核模式回调表(如 GUI 回调)
VOID* UserSharedInfoPtr; // 用户共享信息结构指针
};

ULONG SystemReserved[1]; // 0x30: 系统保留字段
ULONG AtlThunkSListPtr32; // 0x34: ATL thunk 用于 32 位兼容的单链表指针
VOID* ApiSetMap; // 0x38: API Set 映射结构(用于模块重定向)
ULONG TlsExpansionCounter; // 0x3C: TLS 扩展槽计数器
VOID* TlsBitmap; // 0x40: TLS 位图指针(标记可用的 TLS 插槽)
ULONG TlsBitmapBits[2]; // 0x44: TLS 位图本体(64 个槽)
VOID* ReadOnlySharedMemoryBase; // 0x4C: 只读共享内存基地址(Windows 内部用途)
VOID* HotpatchInformation; // 0x50: 热补丁相关结构体
VOID** ReadOnlyStaticServerData; // 0x54: 静态服务器数据的指针数组
VOID* AnsiCodePageData; // 0x58: ANSI 码页数据(LCID 对应)
VOID* OemCodePageData; // 0x5C: OEM 码页数据
VOID* UnicodeCaseTableData; // 0x60: Unicode 大小写映射表
ULONG NumberOfProcessors; // 0x64: 系统中可用的逻辑处理器数量
ULONG NtGlobalFlag; // 0x68: 全局标志位(调试器、特殊分配等标记)
union _LARGE_INTEGER CriticalSectionTimeout; // 0x70: 临界区超时时间(用于死锁检测)
ULONG HeapSegmentReserve; // 0x78: 堆段保留大小
ULONG HeapSegmentCommit; // 0x7C: 堆段提交大小
ULONG HeapDeCommitTotalFreeThreshold;// 0x80: 堆释放总阈值
ULONG HeapDeCommitFreeBlockThreshold;// 0x84: 堆释放单块阈值
ULONG NumberOfHeaps; // 0x88: 当前进程拥有的堆数量
ULONG MaximumNumberOfHeaps; // 0x8C: 最大堆数量
VOID** ProcessHeaps; // 0x90: 堆数组指针(实际堆地址数组)
VOID* GdiSharedHandleTable; // 0x94: GDI 共享句柄表
VOID* ProcessStarterHelper; // 0x98: 启动帮助函数
ULONG GdiDCAttributeList; // 0x9C: GDI DC 属性位图(标识属性状态)
struct _RTL_CRITICAL_SECTION* LoaderLock; // 0xA0: Ldr 模块加载器锁
ULONG OSMajorVersion; // 0xA4: 操作系统主版本号
ULONG OSMinorVersion; // 0xA8: 操作系统次版本号
USHORT OSBuildNumber; // 0xAC: 构建号(Build Number)
USHORT OSCSDVersion; // 0xAE: 客户服务描述版本(如 SP1)
ULONG OSPlatformId; // 0xB0: 平台 ID(Win32 NT = 2)
ULONG ImageSubsystem; // 0xB4: 子系统类型(GUI = 2, CUI = 3)
ULONG ImageSubsystemMajorVersion; // 0xB8: 子系统主版本号
ULONG ImageSubsystemMinorVersion; // 0xBC: 子系统次版本号
ULONG ActiveProcessAffinityMask; // 0xC0: 进程默认亲和性掩码
ULONG GdiHandleBuffer[34]; // 0xC4: 用于 GDI 的句柄缓存(优化)
VOID (*PostProcessInitRoutine)(); // 0x14C: 进程初始化后回调函数
VOID* TlsExpansionBitmap; // 0x150: 扩展 TLS 位图
ULONG TlsExpansionBitmapBits[32]; // 0x154: TLS 扩展槽使用位图
ULONG SessionId; // 0x1D4: 当前 Session 的 ID
union _ULARGE_INTEGER AppCompatFlags; // 0x1D8: 应用兼容性标志
union _ULARGE_INTEGER AppCompatFlagsUser; // 0x1E0: 用户级应用兼容性标志
VOID* pShimData; // 0x1E8: Shim 层数据(兼容性修复层)
VOID* AppCompatInfo; // 0x1EC: 应用兼容性信息结构
struct _UNICODE_STRING CSDVersion; // 0x1F0: 系统版本描述字符串(如 "Service Pack 1")
struct _ACTIVATION_CONTEXT_DATA* ActivationContextData; // 0x1F8: 激活上下文信息(Side-by-Side)
struct _ASSEMBLY_STORAGE_MAP* ProcessAssemblyStorageMap; // 0x1FC: 应用程序集存储映射表
struct _ACTIVATION_CONTEXT_DATA* SystemDefaultActivationContextData; // 0x200: 系统默认激活上下文
struct _ASSEMBLY_STORAGE_MAP* SystemAssemblyStorageMap; // 0x204: 系统程序集映射表
ULONG MinimumStackCommit; // 0x208: 最小堆栈提交大小
struct _FLS_CALLBACK_INFO* FlsCallback; // 0x20C: FLS 回调函数指针
struct _LIST_ENTRY FlsListHead; // 0x210: FLS 数据链表
VOID* FlsBitmap; // 0x218: FLS 插槽使用位图
ULONG FlsBitmapBits[4]; // 0x21C: FLS 插槽实际位图(128 位)
ULONG FlsHighIndex; // 0x22C: 当前最大 FLS 插槽索引
VOID* WerRegistrationData; // 0x230: Windows 错误报告注册数据
VOID* WerShipAssertPtr; // 0x234: Ship Assert 支持结构
VOID* pContextData; // 0x238: 上下文数据(调试器/CLR)
VOID* pImageHeaderHash; // 0x23C: 镜像头部哈希值(完整性验证)
union
{
ULONG TracingFlags; // 0x240: 跟踪标志
struct
{
ULONG HeapTracingEnabled:1; // bit 0: 启用堆跟踪
ULONG CritSecTracingEnabled:1; // bit 1: 启用临界区跟踪
ULONG SpareTracingBits:30; // bit 2-31: 保留
};
};
};

进程防护技巧

进程查找

这里主要是根据进程名查找进程对象 EPROCESS,这是通常进行进程保护的第一步。

遍历进程链表

EPROCESSActiveProcessLinks 字段表示当前活动的进程的 EPROCESS 构成的双向链表,我们可以遍历这个双向链表来查找指定进程名对应的 EPROCESS

然而 EPROCESSActiveProcessLinks 字段在不同版本的操作系统中的偏移不同,一种思路是通过 PsGetProcessId 函数来定位 UniqueProcessId 字段。

对于 32 位我们只需要搜索 8B 80 然后取后面的 4 字节即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; void* __stdcall PsGetProcessId(PEPROCESS Process)
_PsGetProcessId@4 proc near

mov edi, edi ; 标准的“热补丁”对齐占位指令
push ebp
mov ebp, esp

mov eax, [ebp+Process] ; 获取参数:PEPROCESS Process
mov eax, [eax+_EPROCESS.UniqueProcessId] ; 👈 8B 80 xx xx xx xx
; 提取 EPROCESS 结构中的 UniqueProcessId 字段

pop ebp
retn 4 ; __stdcall 调用约定,清理参数

_PsGetProcessId@4 endp

对于 64 位我们只需要取 PsGetProcessId 后面 3 字节偏移位置的 4 字节即可。

1
2
3
4
5
6
7
8
; HANDLE __stdcall PsGetProcessId(_EPROCESS *Process)
PsGetProcessId proc near

mov rax, [rcx+_EPROCESS.UniqueProcessId] ; 👈 48 8B 81 xx xx xx xx
; 获取 _EPROCESS 结构中的 UniqueProcessId 字段
retn

PsGetProcessId endp

由于 UniqueProcessIdActiveProcessLinks 相邻,因此可以定位到 ActiveProcessLinks 字段的偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 获取 _EPROCESS 结构中的 UniqueProcessId 字段偏移
ULONG GetUniqueProcessIdOffset()
{
#ifdef _WIN64
// 64 位版本:通过 PsGetProcessId 后的 3 字节偏移获取 UniqueProcessId 偏移
ULONG offset = *(ULONG*)((PUCHAR)PsGetProcessId + 3); // 获取 PsGetProcessId 后 3 字节偏移
return offset; // 直接返回 UniqueProcessId 的偏移
#else
// 32 位版本:通过特征匹配获取 UniqueProcessId 偏移
PUCHAR base = (PUCHAR)PsGetProcessId; // 获取 PsGetProcessId 函数的基址

for (int i = 0; i < 0x40; i++) {
PUCHAR p = base + i;

// 查找指令:mov eax, [eax + offset] -> 8B 80 xx xx xx xx
if (p[0] == 0x8B && p[1] == 0x80) {
ULONG pidOffset = *(ULONG*)(p + 2); // 获取偏移值(跳过 8B 80)

// 确保 offset 合理
if (pidOffset >= 0x80 && pidOffset < 0x1000) {
return pidOffset; // 返回 UniqueProcessId 的偏移
}
}
}

// 未找到有效的偏移
return (ULONG)-1; // 0xFFFFFFFF
#endif
}

// 计算 ActiveProcessLinks 偏移
ULONG DetectActiveProcessLinksOffset()
{
ULONG UniqueProcessIdOffset = GetUniqueProcessIdOffset();

// 如果返回 -1 表示没找到偏移
if (UniqueProcessIdOffset == (ULONG)-1) {
return (ULONG)-1;
}

// ActiveProcessLinks 紧跟在 UniqueProcessId 后面
return UniqueProcessIdOffset + sizeof(void*);
}

之后就是链表遍历的过程。

注意

SeLocateProcessImageName 通过 EPROCESSSeAuditProcessCreationInfo 字段可以获取完整映像路径,要想与查找的进程名进行需要提取映像路径中的文件名。

提取文件名需要从后向前查找第一个 L'\\' 字符然后取后面的字符串。

wcsrchr 需要以 L'\0' 结尾的字符串,但 UNICODE_STRING.Buffer 只是个 定长缓存,不一定以 L'\0' 结尾,因此需要手动向后遍历查找 L'\\'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <ntddk.h>
#include <ntstrsafe.h>

PEPROCESS FindProcessByNameW(PCWSTR targetName)
{
// 动态检测 ActiveProcessLinks 在 EPROCESS 中的偏移
ULONG activeLinksOffset = DetectActiveProcessLinksOffset();
if (activeLinksOffset == (ULONG)-1) {
DbgPrint("[-] Failed to detect ActiveProcessLinks offset.\n");
return NULL;
}

// 从系统进程开始遍历 ActiveProcessLinks 双向链表
PEPROCESS systemProcess = PsInitialSystemProcess;
PLIST_ENTRY head = (PLIST_ENTRY)((PUCHAR)systemProcess + activeLinksOffset);
PLIST_ENTRY entry = head;

// 初始化目标文件名为 UNICODE_STRING
UNICODE_STRING target;
RtlInitUnicodeString(&target, targetName);

do {
// 从当前链表节点反推出所在的 EPROCESS 结构地址
PEPROCESS process = (PEPROCESS)((PUCHAR)entry - activeLinksOffset);

// 获取完整的映像路径,如:\Device\HarddiskVolumeX\Windows\System32\notepad.exe
UNICODE_STRING *imagePath = NULL;
NTSTATUS status = SeLocateProcessImageName(process, &imagePath);

if (NT_SUCCESS(status) && imagePath && imagePath->Buffer) {
// 提取最后一个反斜杠后的文件名部分
USHORT length = imagePath->Length / sizeof(WCHAR);
USHORT offset = 0;

for (USHORT i = length; i > 0; i--) {
if (imagePath->Buffer[i - 1] == L'\\') {
offset = i;
break;
}
}

// 构造 UNICODE_STRING 表示文件名部分(避免直接使用 _wcsicmp 越界)
UNICODE_STRING fileName = {
.Buffer = &imagePath->Buffer[offset],
.Length = (length - offset) * sizeof(WCHAR),
.MaximumLength = (length - offset) * sizeof(WCHAR)
};

// 不区分大小写比较文件名
if (RtlEqualUnicodeString(&fileName, &target, TRUE)) {
ExFreePool(imagePath);
return process; // 找到匹配进程,立即返回
}

ExFreePool(imagePath);
}

entry = entry->Flink;

} while (entry != head && entry != NULL); // 遍历至循环结束或异常中止

return NULL; // 未找到目标进程
}

枚举进程 Id

在某些场景下(如内核 Rootkit),ActiveProcessLinks 链表可能被恶意修改(如断链)以隐藏进程。这种情况下,仅靠 ActiveProcessLinks 遍历将无法发现目标进程。

Windows 为所有活动进程维护了系统对象句柄表。PsLookupProcessByProcessId 会通过这些内核内部机制而不是链表查找 EPROCESS,因此仍能定位被“断链”隐藏的进程。

提示

进程 ID 通常是 4 的倍数,递增步长为 4。这是因为进程 ID 是句柄,类型为 EXHANDLE,最低 2 位是保留位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <ntifs.h>
#include <ntstrsafe.h>

// 提取路径中的最后一个文件名部分(非 NULL 结尾,手动构造 UNICODE_STRING)
static BOOLEAN ExtractFileNameFromPath(PUNICODE_STRING fullPath, UNICODE_STRING* fileName)
{
if (!fullPath || !fullPath->Buffer || fullPath->Length == 0)
return FALSE;

PWCH start = fullPath->Buffer;
USHORT length = fullPath->Length / sizeof(WCHAR);

// 向前扫描,查找最后一个反斜杠
USHORT i;
for (i = length; i > 0; i--) {
if (fullPath->Buffer[i - 1] == L'\\') {
break;
}
}

// i 是文件名在 fullPath 中的起始索引
fileName->Buffer = &fullPath->Buffer[i];
fileName->Length = (length - i) * sizeof(WCHAR);
fileName->MaximumLength = fileName->Length;
return TRUE;
}

// 根据进程名查找 EPROCESS(使用 SeLocateProcessImageName 获取完整路径)
PEPROCESS FindProcessByNameW(PCWSTR targetName)
{
if (!targetName)
return NULL;

UNICODE_STRING targetStr;
RtlInitUnicodeString(&targetStr, targetName);

for (ULONG_PTR pid = 4; pid < 0x100000; pid += 4)
{
PEPROCESS tempProcess = NULL;
if (!NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)pid, &tempProcess)))
continue;

// 跳过已经退出的进程
if (PsGetProcessExitStatus(tempProcess) != STATUS_PENDING) {
ObDereferenceObject(tempProcess);
continue;
}

// 获取进程映像路径
PUNICODE_STRING imagePath = NULL;
if (!NT_SUCCESS(SeLocateProcessImageName(tempProcess, &imagePath)) || !imagePath) {
ObDereferenceObject(tempProcess);
continue;
}

UNICODE_STRING exeName;
if (ExtractFileNameFromPath(imagePath, &exeName)) {
if (RtlCompareUnicodeString(&exeName, &targetStr, TRUE) == 0) {
ExFreePool(imagePath);
return tempProcess; // 匹配成功,返回
}
}

// 清理并继续
ExFreePool(imagePath);
ObDereferenceObject(tempProcess);
}

return NULL;
}

注意

Windows 内核中的 PEPROCESS 是一种引用计数对象。当调用 PsLookupProcessByProcessId 时系统会对该进程对象 引用计数 +1。这个引用是你“拥有”的,意味着你必须在用完后 调用 ObDereferenceObject减少引用计数,否则会导致内核对象泄漏(内核内存无法释放,进程无法完全终止)。

1
2
3
4
5
6
7
8
PEPROCESS proc = FindProcessByNameW(L"notepad.exe");
if (proc) {
DbgPrint("找到进程: %p\n", proc);
ObDereferenceObject(proc);
}
else {
DbgPrint("未找到目标进程\n");
}

进程隐藏

进程断链

Windows 操作系统通过双向链表来管理所有运行中的进程,进程之间通过 ActiveProcessLinks 字段相互连接。通过从该链表中移除目标进程,可以实现进程隐藏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <ntddk.h>
#include <ntstrsafe.h>

VOID HideProcessByNameW(PCWSTR targetName)
{
PEPROCESS targetProcess = FindProcessByNameW(targetName); // 获取目标进程的 EPROCESS
if (!targetProcess) {
DbgPrint("[-] Process not found.\n");
return; // 没有找到进程,退出
}

// 动态检测 ActiveProcessLinks 在 EPROCESS 中的偏移
ULONG activeLinksOffset = DetectActiveProcessLinksOffset();
if (activeLinksOffset == (ULONG)-1) {
DbgPrint("[-] Failed to detect ActiveProcessLinks offset.\n");
return; // 获取偏移失败,退出
}

// 从 EPROCESS 获取 ActiveProcessLinks 双向链表中的指针
PLIST_ENTRY activeLinks = (PLIST_ENTRY)((PUCHAR)targetProcess + activeLinksOffset);

// 使用 RemoveEntryList API 从链表中移除目标进程
RemoveEntryList(activeLinks);

// 清理目标进程的链表指针,确保该节点不再关联任何链表
InitializeListHead(activeLinks); // 将链表头部的 Flink 和 Blink 指向自己

DbgPrint("[+] Successfully hid process %ws\n", targetName);
}

修改进程 ID

在某些情况下,修改进程的 PID(进程 ID)可能是实现进程隐藏的另一个重要步骤。通过修改 EPROCESS 结构中的 UniqueProcessId 字段,可以伪造进程的 ID,使得它在系统中的任务管理器中无法正常找到被隐藏进程。

提示

由于部分操作系统的进程管理器的实现问题,PID 重复会导致其显示出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
VOID ChangeProcessIdByNameW(PCWSTR targetName, ULONG newPid)
{
PEPROCESS targetProcess = FindProcessByNameW(targetName); // 获取目标进程的 EPROCESS
if (!targetProcess) {
DbgPrint("[-] Process not found.\n");
return; // 没有找到进程,退出
}

// 动态检测 UniqueProcessId 在 EPROCESS 中的偏移
ULONG pidOffset = GetUniqueProcessIdOffset();
if (pidOffset == (ULONG)-1) {
DbgPrint("[-] Failed to detect UniqueProcessId offset.\n");
return; // 获取偏移失败,退出
}

// 获取目标进程的 UniqueProcessId 字段地址
ULONG* pidAddr = (ULONG*)((PUCHAR)targetProcess + pidOffset);

// 修改进程的 PID
*pidAddr = newPid;

DbgPrint("[+] Successfully changed process %ws PID to %lu\n", targetName, newPid);
}

进程保护

BreakOnTermination (Flags bit13)

BreakOnTermination 是一种机制,在进程即将被终止时触发断点。通常用于调试进程的终止过程,帮助开发人员在进程即将结束时进行故障排除或保存重要信息。

如果该标志位置为 1,则当其他进程终止被保护的进程时,系统会触发蓝屏。这种保护机制用于防止重要进程的意外终止,通过检测进程结束时是否存在其他进程的干扰,如果发现不当操作,系统会抛出异常,进而引发蓝屏,以确保系统的完整性和安全性。

ProcessInserted (Flags bit26)

如果 ProcessInserted 标志位设置为 0,则当其他进程尝试打开受保护进程时,它们可以成功获取该进程的句柄,但无法将该句柄插入到自身进程的句柄表中。最终的结果是,该进程无法被正常操作,即使可以获得句柄,仍然无法进行操作(如调试、修改等)。

此标志位用于限制对进程句柄的访问,确保在某些情况下,其他进程无法通过获得句柄对受保护进程进行修改或干预。常见的应用场景包括防止恶意软件或不当操作干扰受保护进程的运行。

注意

ProcessInserted 设置为 0,进程自身也将无法创建线程、访问或操作其他句柄,甚至可能无法执行对自身的管理操作。具体来说,创建新线程、分配内存、访问文件句柄等操作将受到影响,导致进程无法正常执行这些任务。

ProtectedProcess (Flags2 bit11)

ProtectedProcess 是一种通过在操作系统中标记进程来防止其被结束或修改的技术。它可以确保某些关键进程(如操作系统服务或反病毒进程)不被恶意软件或未经授权的操作干扰。

ProtectedProcess 标志与 CreateProcess 函数中的 dwCreationFlags 参数中的 CREATE_PROTECTED_PROCESS 标志位相关。使用 CREATE_PROTECTED_PROCESS 启动的进程会被标记为受保护进程。然而,要使进程成为受保护进程,二进制文件必须具有由 Microsoft 提供的特殊签名。当前,只有 Microsoft 提供的进程和二进制文件才能享受此保护,非 Microsoft 的二进制文件无法直接成为受保护进程。

此标志机制通过严格限制对受保护进程的访问,防止它们被外部操作(如终止、调试或挂起)影响,通常用于保护系统的关键进程或高安全性进程,以保障系统的稳定性和安全性。

线程相关

线程结构

KTHREAD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
struct _KTHREAD
{
struct _DISPATCHER_HEADER Header; // 0x00: 调度器头,支持线程同步、定时器等
volatile ULONGLONG CycleTime; // 0x10: 累计使用的 CPU 时间(时间戳计数)
volatile ULONG HighCycleTime; // 0x18: CycleTime 的高 32 位
ULONGLONG QuantumTarget; // 0x20: 时间片截止时间
VOID* InitialStack; // 0x28: 📌栈底指针
VOID* volatile StackLimit; // 0x2C: 📌栈顶界限(溢出保护)
VOID* KernelStack; // 0x30: 📌当前内核栈指针,用户线程切换的时候保存 ESP
ULONG ThreadLock; // 0x34: 自旋锁,用于线程结构保护
union _KWAIT_STATUS_REGISTER WaitRegister; // 0x38: 等待状态寄存器(压缩状态位)
volatile UCHAR Running; // 0x39: 📌当前是否正在运行(1 表示运行)
UCHAR Alerted[2]; // 0x3A: 📌[0]=内核APC警报, [1]=用户APC警报
union {
struct {
ULONG KernelStackResident:1; // bit 0: 栈是否驻留在内存中
ULONG ReadyTransition:1; // bit 1: 准备态转运行态
ULONG ProcessReadyQueue:1; // bit 2: 线程是否在进程就绪队列中
ULONG WaitNext:1; // bit 3: 是否立即等待下一个事件
ULONG SystemAffinityActive:1; // bit 4: 是否强制使用系统亲和性
ULONG Alertable:1; // bit 5: 📌是否可以中断(用于 APC)
ULONG GdiFlushActive:1; // bit 6: 是否在刷新 GDI 缓存
ULONG UserStackWalkActive:1; // bit 7: 是否正在遍历用户栈
ULONG ApcInterruptRequest:1; // bit 8: 📌是否请求 APC 中断
ULONG ForceDeferSchedule:1; // bit 9: 是否强制延迟调度
ULONG QuantumEndMigrate:1; // bit 10: 是否允许量子结束时迁移
ULONG UmsDirectedSwitchEnable:1; // bit 11: 是否启用 UMS 指定切换
ULONG TimerActive:1; // bit 12: 线程是否激活了计时器
ULONG SystemThread:1; // bit 13: 是否为系统线程(非用户)
ULONG Reserved:18; // bit 14-31: 保留位
};
LONG MiscFlags; // 0x3C: 以上所有位的组合访问
};
union {
struct _KAPC_STATE ApcState; // 0x40: 📌APC 状态,含队列与锁
struct {
UCHAR ApcStateFill[23]; // 填充
CHAR Priority; // 0x57: 当前线程调度优先级
};
};
volatile ULONG NextProcessor; // 0x58: 调度目标 CPU
volatile ULONG DeferredProcessor; // 0x5C: 被延迟分配的 CPU
ULONG ApcQueueLock; // 0x60: APC 队列互斥锁
ULONG ContextSwitches; // 0x64: 上下文切换计数器
volatile UCHAR State; // 0x68: 线程状态(Initialized 等)
CHAR NpxState; // 0x69: 协处理器状态
UCHAR WaitIrql; // 0x6A: 等待时提升的 IRQL
CHAR WaitMode; // 0x6B: 等待模式(内核 / 用户)
volatile LONG WaitStatus; // 0x6C: 等待完成状态
struct _KWAIT_BLOCK* WaitBlockList; // 0x70: 等待块链表指针
union {
struct _LIST_ENTRY WaitListEntry; // 0x74: 等待对象链表节点
struct _SINGLE_LIST_ENTRY SwapListEntry; // 0x74: 用于交换线程的备用单链
};
struct _KQUEUE* volatile Queue; // 0x7C: 所在等待队列
ULONG WaitTime; // 0x80: 等待开始时间
union {
struct {
SHORT KernelApcDisable; // 0x84: 禁止内核 APC
SHORT SpecialApcDisable; // 0x86: 禁止特殊 APC(如调度)
};
ULONG CombinedApcDisable; // 0x84: 合并后的 APC 禁止位
};
VOID* Teb; // 0x88: 📌用户线程环境块 TEB 指针
struct _KTIMER Timer; // 0x90: 内核定时器对象
union {
struct {
ULONG AutoAlignment:1; // bit 0: 栈自动对齐
ULONG DisableBoost:1; // bit 1: 禁用优先级提升
ULONG EtwStackTraceApc1Inserted:1; // bit 2: ETW APC1 插入标志
ULONG EtwStackTraceApc2Inserted:1; // bit 3: ETW APC2 插入标志
ULONG CalloutActive:1; // bit 4: 回调活动中
ULONG ApcQueueable:1; // bit 5: 是否允许 APC 入队
ULONG EnableStackSwap:1; // bit 6: 启用栈切换
ULONG GuiThread:1; // bit 7: 是否 GUI 线程
ULONG UmsPerformingSyscall:1; // bit 8: UMS 正在执行系统调用
ULONG VdmSafe:1; // bit 9: VDM 兼容(16 位支持)
ULONG UmsDispatched:1; // bit 10: 已分派为 UMS 线程
ULONG ReservedFlags:21; // bit 11-31: 保留
};
volatile LONG ThreadFlags; // 0xB8: 所有标志位联合体访问
};
VOID* ServiceTable; // 0xBC: 系统调用服务表(指向 SSDT)
struct _KWAIT_BLOCK WaitBlock[4]; // 0xC0: 最多支持同时等待 4 个同步对象
struct _LIST_ENTRY QueueListEntry; // 0x120: 在线程队列(如工作队列)中的节点
struct _KTRAP_FRAME* TrapFrame; // 0x128: 当前陷阱帧指针(中断或异常时的栈帧)
VOID* FirstArgument; // 0x12C: 初始参数(如新线程的入口参数)
union {
VOID* CallbackStack; // 0x130: 当前正在执行的回调栈地址
ULONG CallbackDepth; // 0x130: 回调嵌套深度
};
UCHAR ApcStateIndex; // 0x134: 📌APC 状态索引(内核/用户)
CHAR BasePriority; // 0x135: 初始基本优先级
union {
CHAR PriorityDecrement; // 0x136: 当前优先级减少值(优先级衰减)
struct {
UCHAR ForegroundBoost : 4; // bit 0-3: 前台线程优先级提升
UCHAR UnusualBoost : 4; // bit 4-7: 特殊调度场景提升
};
};
UCHAR Preempted; // 0x137: 是否被抢占
UCHAR AdjustReason; // 0x138: 优先级调整原因
CHAR AdjustIncrement; // 0x139: 优先级调整增量
CHAR PreviousMode; // 0x13A: 上下文切换前的 CPU 模式(用户/内核)
CHAR Saturation; // 0x13B: 饱和度指标,用于调度策略
ULONG SystemCallNumber; // 0x13C: 上次系统调用号
ULONG FreezeCount; // 0x140: 冻结计数(例如调试器挂起)
volatile struct _GROUP_AFFINITY UserAffinity; // 0x144: 用户设置的 CPU 亲和性
struct _KPROCESS* Process; // 0x150: 📌所属进程的 `_KPROCESS` 指针
volatile struct _GROUP_AFFINITY Affinity; // 0x154: 当前线程亲和性掩码(活动 CPU 集)
ULONG IdealProcessor; // 0x160: 调度器理想的运行 CPU
ULONG UserIdealProcessor; // 0x164: 用户设置的理想处理器编号
struct _KAPC_STATE* ApcStatePointer[2]; // 0x168: 指向内核 / 用户 APC 状态
union {
struct _KAPC_STATE SavedApcState; // 0x170: 保存的 APC 状态(线程挂起/恢复时)
struct {
UCHAR SavedApcStateFill[23]; // 填充对齐
UCHAR WaitReason; // 0x187: 当前线程的等待原因(调试、同步等)
};
};
CHAR SuspendCount; // 0x188: 被挂起次数(非 0 时线程暂停)
CHAR Spare1; // 0x189: 保留
UCHAR OtherPlatformFill; // 0x18A: 多平台兼容保留位
VOID* volatile Win32Thread; // 0x18C: 指向 Win32 子系统线程结构(如 CSR)
VOID* StackBase; // 0x190: 栈基地址(高地址)
union {
struct _KAPC SuspendApc; // 0x194: 用于挂起线程的 APC 对象
struct {
UCHAR SuspendApcFill0[1]; // 0x194
UCHAR ResourceIndex; // 0x195: 分配资源索引
};
struct {
UCHAR SuspendApcFill1[3]; // 0x194
UCHAR QuantumReset; // 0x197: 时间片重置标志
};
struct {
UCHAR SuspendApcFill2[4]; // 0x194
ULONG KernelTime; // 0x198: 已消耗的内核时间
};
struct {
UCHAR SuspendApcFill3[36]; // 0x194
struct _KPRCB* volatile WaitPrcb; // 0x1B8: 当前等待的 PRCB(处理器控制块)
};
struct {
UCHAR SuspendApcFill4[40]; // 0x194
VOID* LegoData; // 0x1BC: LEGO 用户模式调度器数据
};
struct {
UCHAR SuspendApcFill5[47]; // 0x194
UCHAR LargeStack; // 0x1C3: 是否使用大栈(>默认大小)
};
};
ULONG UserTime; // 0x1C4: 已消耗的用户模式时间
union {
struct _KSEMAPHORE SuspendSemaphore; // 0x1C8: 挂起信号量(用于线程恢复)
UCHAR SuspendSemaphorefill[20]; // 填充用
};
ULONG SListFaultCount; // 0x1DC: SList 异常计数(栈溢出等)
struct _LIST_ENTRY ThreadListEntry; // 0x1E0: 📌线程链表节点(进程中的线程列表)
struct _LIST_ENTRY MutantListHead; // 0x1E8: 线程持有的互斥体链表头
VOID* SListFaultAddress; // 0x1F0: 最后一个异常 SList 操作地址
struct _KTHREAD_COUNTERS* ThreadCounters; // 0x1F4: 性能统计计数器
struct _XSTATE_SAVE* XStateSave; // 0x1F8: 扩展浮点 / AVX 状态保存结构
}; // 0x200 bytes

ETHREAD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// 0x2B8 bytes (sizeof)
struct _ETHREAD
{
struct _KTHREAD Tcb; // 0x000: 内核线程结构(线程控制块,KTHREAD)
union _LARGE_INTEGER CreateTime; // 0x200: 线程创建时间(自系统启动以来的时间)
union {
union _LARGE_INTEGER ExitTime; // 0x208: 线程退出时间(0 表示未退出)
struct _LIST_ENTRY KeyedWaitChain; // 0x208: 用于 Keyed Event 同步的链表
};
LONG ExitStatus; // 0x210: 线程退出状态码(如 0 为正常)
union {
struct _LIST_ENTRY PostBlockList; // 0x214: APC 完成时挂起的 IRP 等列表
struct {
VOID* ForwardLinkShadow; // 0x214: 内部链接指针
VOID* StartAddress; // 0x218: 📌用户模式下的线程起始地址
};
};
union {
struct _TERMINATION_PORT* TerminationPort; // 0x21C: 通知线程终止的端口对象
struct _ETHREAD* ReaperLink; // 0x21C: 用于线程回收(Reaper)链表
VOID* KeyedWaitValue; // 0x21C: Keyed Event 用的值
};
ULONG ActiveTimerListLock; // 0x220: 活动定时器链表锁
struct _LIST_ENTRY ActiveTimerListHead; // 0x224: 活动定时器链表头
struct _CLIENT_ID Cid; // 0x22C: 📌客户端 ID(包含进程 ID 和线程 ID)
union {
struct _KSEMAPHORE KeyedWaitSemaphore; // 0x234: Keyed Event 信号量
struct _KSEMAPHORE AlpcWaitSemaphore; // 0x234: ALPC 使用的信号量
};
union _PS_CLIENT_SECURITY_CONTEXT ClientSecurity; // 0x248: 线程安全上下文(用于模拟)
struct _LIST_ENTRY IrpList; // 0x24C: 线程持有的 IRP 链表
ULONG TopLevelIrp; // 0x254: 用于避免 IRP 嵌套递归
struct _DEVICE_OBJECT* DeviceToVerify; // 0x258: 安全检查使用的设备对象
union _PSP_CPU_QUOTA_APC* CpuQuotaApc; // 0x25C: CPU 配额 APC(调控线程使用时间)
VOID* Win32StartAddress; // 0x260: 📌Win32 子系统看到的起始地址
VOID* LegacyPowerObject; // 0x264: 旧版电源对象(已废弃)
struct _LIST_ENTRY ThreadListEntry; // 0x268: 所在进程线程列表的节点
struct _EX_RUNDOWN_REF RundownProtect; // 0x270: Rundown 保护机制(防止销毁中访问)
struct _EX_PUSH_LOCK ThreadLock; // 0x274: 线程对象自旋锁
ULONG ReadClusterSize; // 0x278: 用于文件读取的集群大小优化
volatile LONG MmLockOrdering; // 0x27C: 内存管理器锁顺序调试字段
union {
ULONG CrossThreadFlags; // 0x280: 跨线程共享的状态标志
struct {
ULONG Terminated : 1; // bit 0: 线程已终止
ULONG ThreadInserted : 1; // bit 1: 📌插入线程调度队列
ULONG HideFromDebugger : 1; // bit 2: 📌隐藏线程以防调试
ULONG ActiveImpersonationInfo : 1; // bit 3: 当前线程启用了模拟令牌
ULONG Reserved : 1; // bit 4: 保留位
ULONG HardErrorsAreDisabled : 1; // bit 5: 禁用硬错误弹窗
ULONG BreakOnTermination : 1; // bit 6: 📌线程终止时触发断点(调试用)
ULONG SkipCreationMsg : 1; // bit 7: 跳过创建消息通知
ULONG SkipTerminationMsg : 1; // bit 8: 跳过终止消息通知
ULONG CopyTokenOnOpen : 1; // bit 9: 打开线程时复制其访问令牌
ULONG ThreadIoPriority : 3; // bit 10–12: IO 优先级(0~7)
ULONG ThreadPagePriority : 3; // bit 13–15: 页面优先级(0~7)
ULONG RundownFail : 1; // bit 16: Rundown 阶段失败标记
ULONG NeedsWorkingSetAging : 1; // bit 17: 需要工作集老化处理
};
};
union {
ULONG SameThreadPassiveFlags; // 0x284: 被动线程上下文状态位(当前线程本地)
struct {
ULONG ActiveExWorker : 1; // bit 0: 是否为激活的工作线程(Ex worker)
ULONG ExWorkerCanWaitUser : 1; // bit 1: Ex worker 是否可以等待用户对象
ULONG MemoryMaker : 1; // bit 2: 是否参与内存页生成(如内存映射)
ULONG ClonedThread : 1; // bit 3: 是否为克隆线程(CreateRemoteThreadEx 等)
ULONG KeyedEventInUse : 1; // bit 4: 是否正在使用 KeyedEvent
ULONG RateApcState : 2; // bit 5–6: 用于页面优先级/调度器统计用途
ULONG SelfTerminate : 1; // bit 7: 是否调用 PsTerminateThread 终止自身
};
};
union {
ULONG SameThreadApcFlags; // 0x288: 当前线程用于 APC 管理的状态标志位
struct {
UCHAR Spare : 1; // bit 0: 保留
volatile UCHAR StartAddressInvalid : 1; // bit 1: 启动地址是否无效(调试或错误状态)
UCHAR EtwPageFaultCalloutActive : 1; // bit 2: 是否正在处理 ETW 页面错误回调
UCHAR OwnsProcessWorkingSetExclusive : 1; // bit 3: 是否独占进程工作集
UCHAR OwnsProcessWorkingSetShared : 1; // bit 4: 是否共享进程工作集
UCHAR OwnsSystemCacheWorkingSetExclusive : 1; // bit 5: 是否独占系统缓存工作集
UCHAR OwnsSystemCacheWorkingSetShared : 1; // bit 6: 是否共享系统缓存工作集
UCHAR OwnsSessionWorkingSetExclusive : 1; // bit 7: 是否独占 session 工作集
UCHAR OwnsSessionWorkingSetShared : 1; // bit 8: 是否共享 session 工作集
UCHAR OwnsProcessAddressSpaceExclusive : 1; // bit 9: 是否独占进程地址空间
UCHAR OwnsProcessAddressSpaceShared : 1; // bit 10: 是否共享进程地址空间
UCHAR SuppressSymbolLoad : 1; // bit 11: 禁用符号加载(用于调试)
UCHAR Prefetching : 1; // bit 12: 是否正在预取内存
UCHAR OwnsDynamicMemoryShared : 1; // bit 13: 是否共享动态内存访问
UCHAR OwnsChangeControlAreaExclusive : 1; // bit 14: 是否独占控制区(节段/文件映射)
UCHAR OwnsChangeControlAreaShared : 1; // bit 15: 是否共享控制区
UCHAR OwnsPagedPoolWorkingSetExclusive : 1; // bit 16: 是否独占分页池工作集
UCHAR OwnsPagedPoolWorkingSetShared : 1; // bit 17: 是否共享分页池工作集
UCHAR OwnsSystemPtesWorkingSetExclusive : 1; // bit 18: 是否独占系统 PTE 工作集
UCHAR OwnsSystemPtesWorkingSetShared : 1; // bit 19: 是否共享系统 PTE 工作集
UCHAR TrimTrigger : 2; // bit 20–21: 页面修剪触发器标志
UCHAR Spare1 : 2; // bit 22–23: 保留
UCHAR PriorityRegionActive; // 0x28B: 当前是否处于优先处理区域(内存调度相关)
};
};
UCHAR CacheManagerActive; // 0x28C: 是否参与 Cache Manager 操作
UCHAR DisablePageFaultClustering; // 0x28D: 是否禁用页面错误聚类(优化调入)
UCHAR ActiveFaultCount; // 0x28E: 当前活动页面错误计数
UCHAR LockOrderState; // 0x28F: 锁顺序状态标志(死锁检测辅助)

ULONG AlpcMessageId; // 0x290: 当前正在处理的 ALPC 消息 ID
union {
VOID* AlpcMessage; // 0x294: 当前 ALPC 消息的指针
ULONG AlpcReceiveAttributeSet; // 0x294: ALPC 接收消息时的属性掩码
};
struct _LIST_ENTRY AlpcWaitListEntry; // 0x298: 等待 ALPC 消息时的链表节点
ULONG CacheManagerCount; // 0x2A0: 参与 Cache Manager 调度的次数
ULONG IoBoostCount; // 0x2A4: IO 优先级提升计数
ULONG IrpListLock; // 0x2A8: IRP 列表自旋锁
VOID* ReservedForSynchTracking; // 0x2AC: 同步追踪保留字段
struct _SINGLE_LIST_ENTRY CmCallbackListHead; // 0x2B0: 注册表回调链表头(用于 Cm 注册通知)
};

TEB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
struct _TEB
{
struct _NT_TIB NtTib; // 0x0 存储线程信息块(TIB),包括堆栈指针、异常处理信息等。
VOID* EnvironmentPointer; // 0x1c 指向当前线程环境块的指针,通常包含与线程环境相关的信息。
struct _CLIENT_ID ClientId; // 0x20 存储线程的客户端ID(包含进程ID和线程ID)。
VOID* ActiveRpcHandle; // 0x28 当前活动的 RPC 句柄,用于跟踪线程参与的 RPC 调用。
VOID* ThreadLocalStoragePointer; // 0x2c 指向线程本地存储(TLS)区域的指针。
struct _PEB* ProcessEnvironmentBlock; // 0x30 指向进程环境块(PEB)的指针,包含进程的全局信息。
ULONG LastErrorValue; // 0x34 线程的最后错误值,用于记录线程的错误状态。
ULONG CountOfOwnedCriticalSections; // 0x38 线程拥有的临界区数量,表示该线程管理的临界区的数量。
VOID* CsrClientThread; // 0x3c 指向客户端线程信息的指针。
VOID* Win32ThreadInfo; // 0x40 指向 Windows 32 位线程信息的指针,通常用于存储 Windows 特定的线程信息。
ULONG User32Reserved[26]; // 0x44 保留字段,供用户使用的 32 位操作系统相关数据。
ULONG UserReserved[5]; // 0xac 额外的用户保留数据。
VOID* WOW32Reserved; // 0xc0 32 位应用程序的相关数据,供 WOW(Windows on Windows)支持使用。
ULONG CurrentLocale; // 0xc4 当前线程的区域设置标识符。
ULONG FpSoftwareStatusRegister; // 0xc8 用于存储浮点运算软件状态寄存器的值。
VOID* SystemReserved1[54]; // 0xcc 保留字段,供系统内部使用。
LONG ExceptionCode; // 0x1a4 存储与当前线程异常相关的代码。
struct _ACTIVATION_CONTEXT_STACK* ActivationContextStackPointer; // 0x1a8 指向激活上下文堆栈的指针,通常用于存储与应用程序活动相关的上下文信息。
UCHAR SpareBytes[36]; // 0x1ac 保留字节,供未来使用。
ULONG TxFsContext; // 0x1d0 用于跟踪事务文件系统(TxFs)上下文的字段。
struct _GDI_TEB_BATCH GdiTebBatch; // 0x1d4 与 GDI(图形设备接口)相关的线程批处理信息。
struct _CLIENT_ID RealClientId; // 0x6b4 线程的实际客户端ID,包含进程ID和线程ID。
VOID* GdiCachedProcessHandle; // 0x6bc 存储 GDI 缓存的进程句柄。
ULONG GdiClientPID; // 0x6c0 GDI 客户端进程ID。
ULONG GdiClientTID; // 0x6c4 GDI 客户端线程ID。
VOID* GdiThreadLocalInfo; // 0x6c8 存储 GDI 线程本地信息的指针。
ULONG Win32ClientInfo[62]; // 0x6cc 存储 Windows 32 位客户端信息的数组。
VOID* glDispatchTable[233]; // 0x7c4 OpenGL 调度表,包含图形渲染相关的函数指针。
ULONG glReserved1[29]; // 0xb68 OpenGL 保留字段。
VOID* glReserved2; // 0xbdc 额外的 OpenGL 保留字段。
VOID* glSectionInfo; // 0xbe0 OpenGL 部分信息。
VOID* glSection; // 0xbe4 OpenGL 部分数据。
VOID* glTable; // 0xbe8 OpenGL 表格数据。
VOID* glCurrentRC; // 0xbec 当前渲染上下文。
VOID* glContext; // 0xbf0 OpenGL 上下文。
ULONG LastStatusValue; // 0xbf4 记录线程的最后状态值。
struct _UNICODE_STRING StaticUnicodeString; // 0xbf8 静态 Unicode 字符串,用于存储静态字符串数据。
WCHAR StaticUnicodeBuffer[261]; // 0xc00 静态 Unicode 字符串缓冲区。
VOID* DeallocationStack; // 0xe0c 用于存储内存回收的堆栈指针。
VOID* TlsSlots[64]; // 0xe10 线程本地存储(TLS)槽数组,用于存储每个槽中的 TLS 数据。
struct _LIST_ENTRY TlsLinks; // 0xf10 与 TLS 相关的链表。
VOID* Vdm; // 0xf18 VDM(虚拟 DOS 模式)相关数据。
VOID* ReservedForNtRpc; // 0xf1c 保留给 NT RPC 的数据。
VOID* DbgSsReserved[2]; // 0xf20 用于调试 SS(子系统)相关的保留数据。
ULONG HardErrorMode; // 0xf28 用于线程处理硬错误模式的状态。
VOID* Instrumentation[9]; // 0xf2c 用于线程的性能监控和调试数据的数组。
struct _GUID ActivityId; // 0xf50 当前线程的活动标识符(GUID)。
VOID* SubProcessTag; // 0xf60 子进程标记。
VOID* EtwLocalData; // 0xf64 本地 ETW(事件跟踪)数据。
VOID* EtwTraceData; // 0xf68 ETW 追踪数据。
VOID* WinSockData; // 0xf6c 与 Windows Sockets(WinSock)相关的数据。
ULONG GdiBatchCount; // 0xf70 GDI 批量计数。
union
{
struct _PROCESSOR_NUMBER CurrentIdealProcessor; // 0xf74 当前理想处理器的编号。
ULONG IdealProcessorValue; // 0xf74 理想处理器的数值表示。
struct
{
UCHAR ReservedPad0; // 0xf74 保留字段。
UCHAR ReservedPad1; // 0xf75 保留字段。
UCHAR ReservedPad2; // 0xf76 保留字段。
UCHAR IdealProcessor; // 0xf77 理想处理器的编号。
};
};
ULONG GuaranteedStackBytes; // 0xf78 保证的栈空间字节数。
VOID* ReservedForPerf; // 0xf7c 保留给性能监控的空间。
VOID* ReservedForOle; // 0xf80 保留给 OLE(对象链接和嵌入)相关数据。
ULONG WaitingOnLoaderLock; // 0xf84 用于表示当前线程是否在等待加载锁。
VOID* SavedPriorityState; // 0xf88 保存的线程优先级状态。
ULONG SoftPatchPtr1; // 0xf8c 软件补丁指针1。
VOID* ThreadPoolData; // 0xf90 线程池数据。
VOID** TlsExpansionSlots; // 0xf94 扩展的 TLS 槽数组。
ULONG MuiGeneration; // 0xf98 MUI(多语言用户界面)版本。
ULONG IsImpersonating; // 0xf9c 标志,表示线程是否在模拟其他安全主体。
VOID* NlsCache; // 0xfa0 国家语言支持(NLS)缓存。
VOID* pShimData; // 0xfa4 与程序兼容性(Shim)相关的数据。
ULONG HeapVirtualAffinity; // 0xfa8 堆的虚拟亲和性。
VOID* CurrentTransactionHandle; // 0xfac 当前事务的句柄。
struct _TEB_ACTIVE_FRAME* ActiveFrame; // 0xfb0 活动帧信息。
VOID* FlsData; // 0xfb4 与 FLS(线程本地存储)相关的数据。
VOID* PreferredLanguages; // 0xfb8 用户首选语言列表。
VOID* UserPrefLanguages; // 0xfbc 用户偏好的语言列表。
VOID* MergedPrefLanguages; // 0xfc0 合并的语言偏好列表。
ULONG MuiImpersonation; // 0xfc4 MUI 模拟状态。
union
{
volatile USHORT CrossTebFlags; // 0xfc8 用于线程的跨 TEB 标志。
USHORT SpareCrossTebBits:16; // 0xfc8 备用的跨 TEB 位。
};
union
{
USHORT SameTebFlags; // 0xfca 同一线程环境块(TEB)标志
struct
{
USHORT SafeThunkCall:1; // bit 0: 安全调用标志
USHORT InDebugPrint:1; // bit 1: 当前线程处于调试打印模式
USHORT HasFiberData:1; // bit 2: 当前线程拥有纤程数据
USHORT SkipThreadAttach:1; // bit 3: 跳过线程附加操作
USHORT WerInShipAssertCode:1; // bit 4: 标志,表示线程处于断言代码中
USHORT RanProcessInit:1; // bit 5: 标志,表示进程初始化已完成
USHORT ClonedThread:1; // bit 6: 标志,表示该线程为克隆线程
USHORT SuppressDebugMsg:1; // bit 7: 是否抑制调试信息
USHORT DisableUserStackWalk:1; // bit 8: 禁用用户栈跟踪
USHORT RtlExceptionAttached:1; // bit 9: 标志,表示 RTL 异常已附加
USHORT InitialThread:1; // bit 10: 标志,表示这是初始线程
USHORT SpareSameTebBits:5; // bit 11-15: 备用位,暂时未使用
};
};
VOID* TxnScopeEnterCallback; // 0xfcc 事务作用域进入回调函数。
VOID* TxnScopeExitCallback; // 0xfd0 事务作用域退出回调函数。
VOID* TxnScopeContext; // 0xfd4 事务作用域上下文数据。
ULONG LockCount; // 0xfd8 锁计数,表示当前线程持有的锁的数量。
ULONG SpareUlong0; // 0xfdc 备用的 ULONG 数据。
VOID* ResourceRetValue; // 0xfe0 资源返回值。
};

线程切换

线程切换(Thread Context Switch)是指操作系统将 CPU 执行权从一个线程转移到另一个线程的过程。这个过程中,系统需要保存当前线程的上下文(CPU 寄存器、程序计数器、栈指针等),并恢复另一个线程的上下文,使其可以从上次停止的位置继续执行。

在 Windows 内核中,线程对象由结构体 _KTHREAD 表示,其 State 字段(类型为 KTHREAD_STATE)用于标识线程当前所处的调度阶段。该状态直接影响调度器是否会选择该线程参与运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// 线程调度状态(KTHREAD_STATE)
// 表示线程在调度器中的生命周期状态。
//
typedef enum _KTHREAD_STATE {
Initialized, // (0)已初始化:线程对象已构造但尚未调度
Ready, // (1)就绪:线程已插入就绪队列,等待运行
Running, // (2)运行中:线程正在某处理器上执行
Standby, // (3)待命:被选为下一个要运行的线程
Terminated, // (4)已终止:线程执行完毕,等待资源释放
Waiting, // (5)等待中:线程正在等待某个事件或超时
Transition, // (6)过渡态:资源尚未准备好,暂不可调度
DeferredReady, // (7)延迟就绪:满足就绪条件但延后入队
GateWait // (8)等待 Gate:等待底层同步对象 Gate
} KTHREAD_STATE;

Windows 的线程切换可以分为两种主要类型:

  • 抢占式线程切换(Preemptive Context Switch) :由系统调度器(Scheduler)主动发起,强制当前线程让出 CPU。这个过程非线程自身控制,可能在任意用户态或内核态发生。常见的触发过程如下:
    • 当前线程的时间片耗尽(Quantum 用尽);
    • 有更高优先级的线程进入 Ready 状态;
    • 电源管理事件(如睡眠/唤醒);
    • 核心调度器策略判断需换出当前线程。
  • 协作式线程切换(Voluntary / Cooperative Context Switch) :线程自身调用系统服务(如阻塞调用)主动放弃 CPU 使用权。这种情况是线程主动调用内核 API 完成,对于 UI 线程、I/O 密集型线程非常常见。常见的触发方式包括:
    • 调用休眠或等待类 API,例如 Sleep()WaitForSingleObject()NtDelayExecution()
    • 调用 KiSwapThread() 主动让出 CPU;
    • 执行同步或阻塞型 I/O 操作(如 ReadFile(),在数据尚未就绪时会导致线程阻塞)。

线程切换过程

不论是哪种线程切换方式,最终线程切换都是调用 KiSwapContext 函数完成。这个函数是 Windows 内核调度器中的线程上下文切换包装函数,具体作用是保存调用者环境,准备参数(OldThread / NewThread / WaitIrql / KPCR),调用核心的 _SwapContext 函数进行线程切换,然后恢复环境返回

提示

由于这个函数内部发生了线程切换,因此从这个函数返回时的堆栈不一定是这个函数调用时的堆栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@KiSwapContext@8 proc near               ; KiSwapContext(int OldThread, int NewThread)
; 用于调度器切换线程上下文

; ------------------------------------------------------------
; 保存调用者现场(用于保护寄存器)
; ------------------------------------------------------------

sub esp, 10h ; 为保存 ebx/esi/edi/ebp 开辟栈空间
mov [esp+10h-4], ebx ; 保存 ebx(寄存器变量)
mov [esp+10h-8], esi ; 保存 esi(新线程指针)
mov [esp+10h-0Ch], edi ; 保存 edi(旧线程指针)
mov [esp+10h-10h], ebp ; 保存 ebp(调用者帧指针)

; ------------------------------------------------------------
; 获取当前处理器的 KPCR(fs:[0] 存储 _KPCR.SelfPcr 指针)
; ------------------------------------------------------------

mov ebx, large fs:_KPCR.SelfPcr ; KPCR(核本地数据)基址 => ebx

; ------------------------------------------------------------
; 参数处理与寄存器设置
; ------------------------------------------------------------

mov edi, ecx ; edi = OldThread(旧线程)
mov esi, edx ; esi = NewThread(新线程)

movzx ecx, [edi+_KTHREAD.WaitIrql] ; ecx = 旧线程的等待 IRQL,用于 SwapContext 控制

; ------------------------------------------------------------
; 调用 SwapContext(核心上下文切换逻辑)
; 参数:ECX=WaitIrql,ESI=NewThread,EDI=OldThread,EBX=KPCR
; ------------------------------------------------------------

call _SwapContext@0 ; 进入调度器切换线程上下文

; ------------------------------------------------------------
; 恢复调用者现场(恢复保存的寄存器)
; ------------------------------------------------------------

mov ebp, [esp+10h-10h] ; 恢复调用者的 ebp
mov edi, [esp+10h-0Ch] ; 恢复旧线程指针
mov esi, [esp+10h-8] ; 恢复新线程指针
mov ebx, [esp+10h-4] ; 恢复 ebx

add esp, 10h ; 回收本地栈空间
retn ; 返回调用者(调度完成)

@KiSwapContext@8 endp

_SwapContext 是线程切换的核心函数,我们主要关注其中的:

  • 切换内核栈:ESP <=> KTHREAD.KernelStack
  • 切换 CR3CR3 <=> KTHREAD.DirectoryTableBase

除此之外,由于 CPU 核心不是线程独占的,因此线程切换的时候还需要更新与 CPU 核心绑定的结构中跟新线程相关的信息:

  • 更新 TSS 中的 ESP0 指向新内核栈的 TRAP_FRAME 尾部。
  • 更新 KPCR 和 GDT 表中对应的 FS 段指向新线程的 TEB
  • 更新 KPCR 中的异常链表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
_SwapContext@0 proc near                     ; 上下文切换主函数,保存当前线程状态,切换到目标线程
; 原型(按调用约定及调用点推断):
; int __stdcall _SwapContext(
; _KPCR *Pcr, ← 传入 ebx
; _KTHREAD *OldThread, ← 传入 edi
; _KTHREAD *NewThread, ← 传入 esi
; UCHAR WaitIrql ← 传入 ecx
; );

; 参数说明:
; ebx = 当前处理器的 KPCR(Kernel Processor Control Region)
; edi = 老线程(即当前正在运行的线程,_KTHREAD *)
; esi = 新线程(即即将被切换进来的线程,_KTHREAD *)
; ecx = 当前线程的 WaitIrql,用于指示切换时的中断请求级别

; ------------------------------------------------------------
; 检查目标线程是否已被其他处理器标记为 Running 状态;
; 若已运行,则自旋等待(避免多核同时调度该线程);
; ------------------------------------------------------------

CheckRunningFlag:
cmp [esi+_ETHREAD.Tcb.Running], 0 ; 检查目标线程 Running 状态位(Running == 1 表示该线程正在执行中)
jz SetRunning ; 若未运行,跳转设置 Running 状态为 1
pause ; 自旋等待,减轻总线压力,适用于多处理器下等待资源
jmp CheckRunningFlag ; 循环检查直到 Running 被清除为止(即等待其他处理器完成切换)
; ------------------------------------------------------------
; 设置目标线程为 Running 状态;
; 同时统计老线程的实际运行时间(基于 TSC)并更新当前 PRCB 的运行周期;
; ------------------------------------------------------------

SetRunning:
mov [esi+_ETHREAD.Tcb.Running], 1 ; 将目标线程标记为 Running,防止其他处理器并发调度该线程
push ecx ; 保存 ecx(WaitIrql),后续可能被 clobber
cli ; 清除中断标志位,避免在更新周期时发生中断切换
dec [ebx+_KPCR.PrcbData.NestingLevel] ; 将 NestingLevel 减 1,进入调度器内部处理(防止递归调度)

rdtsc ; 获取当前时间戳计数器(EDX:EAX = TSC 当前值,单位为周期)
sub eax, [ebx+_KPCR.PrcbData.StartCycles] ; 当前 TSC(低)- 上次记录的起始 TSC(低) = 本次运行周期(低)
sbb edx, [ebx+_KPCR.PrcbData.StartCycles+4] ; 当前 TSC(高)- 上次记录的 TSC(高) - 借位

add [ebx+_KPCR.PrcbData.CycleTime], eax ; 将本次运行周期加到 PRCB 的累计周期(低位)
adc [ebx+_KPCR.PrcbData.CycleTime+4], edx ; 累加高位并处理进位

add [ebx+_KPCR.PrcbData.StartCycles], eax ; 更新 StartCycles 为当前 TSC,作为下次计时起点(低位)
adc [ebx+_KPCR.PrcbData.StartCycles+4], edx ; 高位更新

; ------------------------------------------------------------
; 检查线程调度控制标志位:
; 若启用了 CpuThrottled(限速)或 CounterProfiling(性能分析),则进入特殊路径;
; ------------------------------------------------------------

test [esi+_KTHREAD.Header.ThreadControlFlags], 5 ; bit0: CpuThrottled, bit2: CounterProfiling
; 若线程启用限速或被调度用于分析,进入特殊处理逻辑
jnz HandleProfiling ; 分支到 profiling/throttling 特殊处理流程

; ------------------------------------------------------------
; 正常路径继续:更新调度信息,保存老线程上下文;
; ------------------------------------------------------------

ContinueContextSwitch:
sti ; 恢复中断使能,允许抢占中断(已完成时间计算)
inc [ebx+_KPCR.ContextSwitches] ; 增加当前处理器上的上下文切换计数器

push [ebx+_KPCR.NtTib.ExceptionList] ; 保存老线程的异常处理链表头(挂接在 TIB 中)

; ------------------------------------------------------------
; 判断老线程是否使用过浮点环境(FPU);
; 若使用过则保存其 FPU 状态;
; ------------------------------------------------------------

mov ebp, cr0 ; 读取 CR0 控制寄存器(判断 FPU 是否启用)
movsx eax, [edi+_KTHREAD.NpxState] ; 获取老线程的 NpxState(记录 FPU 使用情况)
test al, al ; NpxState == 0 表示未使用 FPU
jz SkipFpuSave ; 若未使用,跳过 FPU 保存

mov ecx, [edi+_KTHREAD.InitialStack] ; 获取老线程的初始栈地址(ESP 原始值)
test ebp, 0Eh ; 检查 EM(bit2)或 TS(bit3)是否设置
jz SkipCr0Restore
and ebp, 0FFFFFFF1h ; 清除 EM 和 TS,允许访问浮点指令
mov cr0, ebp ; 更新 CR0

SkipCr0Restore:
lea ecx, [ecx-210h] ; 栈顶下移 0x210 字节用于保存 fxsave 区域
cdq ; 清空 edx(部分兼容性指令,不影响 fxsave)
fxsave [ecx] ; 使用 fxsave 保存 FPU/MMX/XMM 状态
and [edi+_KTHREAD.NpxState], 0F8h ; 清除 NpxState 中的 TS 标志(bit0~2)

mov [ebx+_KPCR.PrcbData.NpxThread], 0 ; 清除 PRCB 中记录的 FPU 所属线程(表示当前未使用 FPU)

SkipFpuSave:
; ------------------------------------------------------------
; 📌切换到新线程的内核栈;
; ------------------------------------------------------------

mov [edi+_KTHREAD.KernelStack], esp ; 保存老线程当前栈顶地址
mov eax, [esi+_KTHREAD.InitialStack] ; 取新线程初始栈指针(备用)
mov esp, [esi+_KTHREAD.KernelStack] ; 切换到新线程的内核栈顶(ESP)

; ------------------------------------------------------------
; 📌若新旧线程属于不同进程,则切换地址空间(更新 CR3);
; ------------------------------------------------------------

mov ebp, [esi+_KTHREAD.ApcState.Process] ; 新线程所属进程对象(EPROCESS)
mov eax, [edi+_KTHREAD.ApcState.Process] ; 老线程所属进程
cmp ebp, eax
jz SkipCr3Switch ; 若是同一进程,无需切换 CR3

mov ecx, [ebx+_KPCR.SetMemberCopy] ; 当前处理器的处理器位图(用于 ActiveProcessors)
lock xor [ebp+_KPROCESS.ActiveProcessors.Bitmap], ecx ; 将当前处理器标记为属于新进程
lock xor [eax+_KPROCESS.ActiveProcessors.Bitmap], ecx ; 从老进程清除该处理器标记

mov ecx, [ebp+_KPROCESS.LdtDescriptor.LimitLow] ; 检查新进程是否启用 LDT
or ecx, [eax+_KPROCESS.LdtDescriptor.LimitLow]
jnz SwitchToLdt ; 若任一进程启用了 LDT,则切换 LDT 描述符

mov eax, [ebp+_EPROCESS.Pcb.DirectoryTableBase] ; 新进程页目录表基址
mov cr3, eax ; 更新页目录寄存器 CR3(切换地址空间)

SkipCr3Switch:
; ------------------------------------------------------------
; 📌设置 TSS(任务状态段)中的 esp0 和 IoMapBase 字段;
; esp0 是任务切换后 CPU 自动加载到栈指针的值;
; IoMapBase 定义了 I/O 权限位图的起始位置;
; ------------------------------------------------------------

mov ecx, [esi+_ETHREAD.Tcb.InitialStack] ; 获取新线程初始栈(未减偏移)
lea eax, [ecx-210h] ; 计算 FPU 状态保存区首地址(对齐 esp0)
test byte ptr [eax-1Ah], 2 ; 检查 EFLAGS 的 bit 17(VM)属性,是否为 Virtual-8086 模式
jnz SetTss ; 若是 V86 模式,直接设置 esp0
sub eax, 10h ; 否则为普通模式,跳过保存 V86 模式 4 个段寄存器所需的空间

SetTss:
mov edx, [ebx+_KPCR.TssCopy] ; 获取当前处理器的 TSS(任务状态段)地址
mov [edx+_KTSS.Esp0], eax ; 设置 esp0 为当前栈地址(任务切换后 CPU 使用)
mov ax, [ebp+_KPROCESS.IopmOffset] ; 获取新进程的 I/O 位图偏移(决定是否允许直接访问 I/O)
mov [edx+_KTSS.IoMapBase], ax ; 设置 IOPM 基址

; ------------------------------------------------------------
; 若启用了 ETW 上下文切换事件记录,则跳转进入日志记录路径;
; 否则进入正常路径继续上下文切换;
; ------------------------------------------------------------

test ds:EtwLogFlags, 4 ; 检查 ETW 是否启用了线程切换日志(bit2)
jnz LogEtwContextSwitch ; 若启用,进入事件记录流程

; ------------------------------------------------------------
; 清除老线程 Running 标志,标记其不再运行;
; 同时清除 GS 寄存器(常用于 TLS);
; ------------------------------------------------------------

mov [edi+_ETHREAD.Tcb.Running], 0 ; 清除老线程 Running 标志,标记其调度结束
xor eax, eax
mov gs, eax ; 清空 GS(线程局部存储指针),防止误引用

; ------------------------------------------------------------
; 判断新线程是否需要恢复 FPU 环境;
; 若需要则执行 xrstor 恢复;
; ------------------------------------------------------------

movsx eax, [esi+_ETHREAD.Tcb.NpxState] ; 获取新线程的 NPX 状态
mov ebp, cr0 ; 加载当前 CR0 控制寄存器
and eax, 0FFFFFFF8h ; 清除低 3 位(TS/EM/MP),保留状态高位
jz SkipXrstor ; 若为 0,说明新线程无需恢复 FPU,跳过

test ebp, 0Eh ; 检查 EM / TS 位是否被置位(不允许浮点操作)
jz RestoreFpu ; 若未置位,可直接执行 xrstor
and ebp, 0FFFFFFF1h ; 否则清除 EM / TS 位
mov cr0, ebp ; 写回 CR0,允许浮点操作

RestoreFpu:
cdq ; 清空 EDX
xrstor byte ptr [ecx-210h] ; 恢复新线程的 FPU/MMX/SSE 状态(从已分配栈中恢复)

SkipXrstor:
mov edx, [ecx-14h] ; 恢复栈上的临时 esp 值(作为 context 用)
test [esi+_ETHREAD.Tcb.NpxState], 7 ; 检查 NpxState 的低 3 位,判断是否需要特殊处理
jnz CheckFpuFlags

or edx, ds:_NpxStateNotLoaded ; 若未使用 FPU,则合并标志表示不加载
CheckFpuFlags:
mov eax, ebp
and eax, 0FFFFFFF1h ; 清除 TS/EM 等位
or edx, eax ; 合并恢复 esp 与控制位
cmp edx, ebp
jnz ReloadCr0 ; 若合并后不一致,更新 CR0

; ------------------------------------------------------------
; 更新 KPCR 中当前进程、线程信息;
; 📌设置 FS 段选择器,用于访问 TEB;
; ------------------------------------------------------------

mov ebp, [esi+_ETHREAD.Tcb.ApcState.Process] ; 新线程所属进程
mov eax, [esi+_ETHREAD.Tcb.Teb] ; 新线程的 TEB 指针
mov [ebx+_KPCR.Used_Self], eax ; 设置 KPCR 中指向当前线程 TEB 的指针

mov ecx, [ebx+_KPCR.GDT] ; 获取当前处理器 GDT 表基址
mov [ecx+3Ah], ax ; 设置 FS 段选择器低 16 位
shr eax, 10h
mov [ecx+3Ch], al ; 设置 FS 段选择器中间 8 位
mov [ecx+3Fh], ah ; 设置 FS 段选择器高 8 位

inc [esi+_ETHREAD.Tcb.ContextSwitches] ; 增加线程的切换次数(用于调度统计)

pop [ebx+_KPCR.NtTib.ExceptionList] ; 恢复异常处理链

pop ecx ; 恢复原始 ecx(WaitIrql)

; ------------------------------------------------------------
; 检查当前是否存在活动的 DPC(延迟过程调用);
; 若存在则进入蓝屏路径(禁止在上下文切换中执行 DPC);
; ------------------------------------------------------------

cmp [ebx+_KPCR.PrcbData.DpcRoutineActive], 0 ; 判断是否正在运行 DPC
jnz FatalBugcheckDpc ; 若是,触发 bugcheck

cmp [esi+_ETHREAD.Tcb.ApcState.KernelApcPending], 0 ; 检查是否存在内核 APC 挂起
jnz DeliverKernelApc ; 若有挂起 APC,立即送达

xor eax, eax
retn

协作式线程切换

协作式线程切换即线程主动切换让出 CPU,主要通过 KiSwapThread 函数实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//
// KiSwapThread
//
// 描述:
// 处理线程切换逻辑,保存当前线程的运行时间,选择可执行线程(NextThread / ReadyList / IdleThread),
// 并根据上下文与 APC 状态决定是否执行实际上下文切换。
//
// 参数:
// OldKThread - 当前线程(KTHREAD)指针
// Prcb - 当前处理器的 PRCB 结构指针
//
// 返回:
// 返回 OldKThread->WaitStatus,表示等待完成状态
//
LONG_PTR
FASTCALL
KiSwapThread (
IN PKTHREAD OldThread,
IN PKPRCB CurrentPrcb
)
{
PKTHREAD NewThread;
PKTHREAD IdleThread;
KIRQL NewIrql;
ULONGLONG Timestamp, Delta;
BOOLEAN Success;
LONG WaitStatus;

// ------------------------------------------------------------
// 若延迟就绪链非空,先行处理(如 IoReady、ApcReady 等)
// ------------------------------------------------------------
if (Prcb->DeferredReadyListHead.Next != NULL) {
KiProcessThreadWaitList(Prcb, TRUE, FALSE, FALSE);
}

_disable();

// ------------------------------------------------------------
// 更新当前线程的 TSC 周期并累加至其运行时间
// ------------------------------------------------------------
PKTHREAD CurrentThread = Prcb->CurrentThread;
Prcb->NestingLevel = 1;
Timestamp = __rdtsc();
Delta = Timestamp - Prcb->StartCycles;

CurrentThread->CycleTime.QuadPart += Delta;
CurrentThread->HighCycleTime = CurrentThread->CycleTime.HighPart;

// 若启用了 TSC Profiling 或性能计数,执行统计更新
if (CurrentThread->Header.CycleProfiling) {
PsChargeProcessCpuCycles(Prcb, Delta, (ULONG)(Delta >> 32));
}
if (CurrentThread->Header.CounterProfiling) {
KiEndCounterAccumulation(CurrentThread);
}

Prcb->StartCycles = Timestamp;

_enable();

// ------------------------------------------------------------
// 📌获取 PRCB 自旋锁,并查找可调度线程(优先 KiSearchForNewThread,其次 NextThread,再次 IdleThread)
//
// - KiSearchForNewThread:由调度器找到的下一执行线程,优先调度。
// - Prcb->NextThread:当前处理器就绪队列中挑选的最高优先级线程。
// - Prcb->IdleThread:当前没有线程可调度时使用的空闲线程。
// ------------------------------------------------------------
KiAcquirePrcbLock(Prcb);

NewThread = KiSearchForNewThread(Prcb, FALSE);
if (!NewThread) {
NewThread = Prcb->NextThread;
if (NewThread != NULL) {
Prcb->NextThread = NULL;
} else {
NewThread = Prcb->IdleThread;
}

Prcb->CurrentThread = NewThread;
NewThread->State = Running;
}

// ------------------------------------------------------------
// 若新线程已被其他处理器抢占(Running),退回 IdleThread
// ------------------------------------------------------------
if (NewThread != Prcb->IdleThread &&
NewThread != OldKThread &&
NewThread->Running)
{
NewThread->State = Standby;
Prcb->NextThread = NewThread;

NewThread = Prcb->IdleThread;
NewThread->State = Running;
Prcb->CurrentThread = NewThread;
}

KiReleasePrcbLock(Prcb);

NewIrql = OldKThread->WaitIrql;

// ------------------------------------------------------------
// 若新线程仍为当前线程(即调度未发生),仅执行统计与 APC 检查
// ------------------------------------------------------------
if (OldKThread == NewThread) {
Success = FALSE;

if (!NewThread->ApcState.KernelApcPending ||
NewThread->SpecialApcDisable != 0 ||
NewIrql != 0)
{
Success = FALSE;
} else {
Success = TRUE;
}

_disable();

PKTHREAD NewCurrent = Prcb->CurrentThread;
ULONGLONG TscNow = __rdtsc();
ULONGLONG CpuCycle = TscNow - Prcb->StartCycles + Prcb->CycleTime;

Prcb->CycleTime = CpuCycle;
Prcb->HighCycleTime = (ULONG)(CpuCycle >> 32);
Prcb->StartCycles = TscNow;

if (NewCurrent->Header.CounterProfiling) {
KiBeginCounterAccumulation(NewCurrent, FALSE);
}

Prcb->NestingLevel = 0;
_enable();

if (EtwLogFlags & EVENT_TRACE_FLAG_DISPATCHER) {
EtwTraceContextSwap(OldKThread, OldKThread);
}
}
// ------------------------------------------------------------
// 📌执行上下文切换
// ------------------------------------------------------------
else {
Success = KiSwapContext(OldKThread, NewThread);
}

// ------------------------------------------------------------
// 返回 WaitStatus;若线程开启了定时器,则维护 WaitBlock 链
// ------------------------------------------------------------
WaitStatus = OldKThread->WaitStatus;

if (OldKThread->MiscFlagsStruct.TimerActive &&
!KiCancelTimer(OldKThread->Timer, TRUE))
{
OldKThread->WaitBlock[3].BlockState = BlockStateInactive;
OldKThread->Timer.Header.WaitListHead.Flink = &OldKThread->WaitBlock[3].WaitListEntry;
OldKThread->Timer.Header.WaitListHead.Blink = &OldKThread->WaitBlock[3].WaitListEntry;
}

// ------------------------------------------------------------
// 若发生 APC 调度请求,立即执行 APC 分发
// ------------------------------------------------------------
if (Success) {
KfLowerIrql(APC_LEVEL);
KiDeliverApc(FALSE, NULL, NULL);
}

KfLowerIrql(NewIrql);
return WaitStatus;
}

KiSwapThread 首先通过 KiSearchForNewThread 找到要切换到的线程,然后调用 KiSwapContext 完成线程切换。

KiSearchForNewThread 函数会选择一个最合适的线程来运行在当前处理器上,涉及 Windows 内核调度器的任务调度算法,在不同版本的具体实现不同。但是总的来说逻辑都是优先使用本地资源,必要时进行跨核抢占

下面这个版本代码的主要逻辑为:

  • [Step 1]:使用预选线程(Prcb->NextThread

    • 若调度器之前已选中线程(例如抢占迁移时设置),直接切换该线程为当前线程;
    • 设置线程状态为 Running,并清除 NextThread 字段;
    • 不进行任何就绪队列遍历,快速完成调度。
  • [Step 2]:本地就绪队列选择线程

    • 调用 KiSelectReadyThread 从当前 PRCB 的本地 ReadyList 中选择优先级最高的线程;
    • 若调度为 idle 类型(IsIdleSchedule == TRUE):
      • 清除本核心的 NotUsedCoreProcessor 位;
      • 清除本组的 UsedCoreProcessor 中的 CoreProcessorSet,表明该核心已被重新使用。
  • [Step 3]:本地无线程且非 idle 调度

    • IsIdleSchedule == FALSE 且未找到线程,说明当前核心资源空闲;
    • 将当前核心在 NotUsedCoreProcessor 中置位,表示本核心目前未参与运行;
    • 若该 PRCB 所属的所有核心都 idle(即 CoreProcessorSet 的全部位都在 NotUsedCoreProcessor 中):
      • 将 CoreProcessorSet 添加到 UsedCoreProcessor,允许其他节点迁移线程到该组。
  • [Step 4]:本地调度失败,释放 PRCB 锁

    • 因后续需要访问其他处理器或 Node,为避免死锁,在继续之前先释放当前 PRCB 的锁。
  • [Step 5]:尝试公平调度(PsCpuFairShareEnabled

    • 若启用了公平调度策略,则尝试从 idle-only 队列中拉取待执行线程;
    • 若成功(通过 PsReleaseThreadFromIdleOnlyQueue),则调度完成,返回该线程;
    • 否则进入跨 NUMA 节点线程窃取阶段。
  • [Step 6]:NUMA 感知线程窃取

    • 遍历所有与当前 PRCB 属于同一个 Group 的 NUMA 节点,查找活跃核心并尝试从其就绪队列中“抢线程”:
      • 对每个目标节点:
        • 构造跨核心调度掩码 ScanSet(仅包含活跃核心);
        • 遍历活跃核心,获取其对应的 TargetPrcb
        • 加锁目标 PRCB(按地址顺序双锁当前和目标 PRCB);
        • 若目标 PRCBReadySummary,可尝试使用 KiFindReadyThread 抢占线程;
        • 若抢占成功,将该线程设置为 Running,并更新本地 PRCB
        • 同时清除本核心的空闲标志。
    • 若目标 PRCB 没有可抢线程,则解锁并尝试下一个 NUMA 节点或处理器。
    • 如果在尝试过程中发现本地 Prcb->NextThread 被设置(例如远程迁移),优先使用它,并终止搜索。
  • [Step 7]:所有路径失败

    • 若所有调度路径(本地、本组、NUMA、IdleOnly)均无可调度线程:
      • 返回 NULL,调度器进入 Idle 状态,当前核心维持空闲;
      • IdleThread 维持处理器活跃,等待下一次中断或调度请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//
// KiSearchForNewThread
//
// 函数作用:
// 这是 Windows 内核调度器中的核心函数之一,负责为指定的处理器(PRCB)
// 查找一个可以运行的线程(PKTHREAD),该函数通常在当前处理器处于空闲状态,
// 或者需要进行线程切换时被调用。
//
// 调度策略:
// - 优先使用 PRCB->NextThread 中预选线程(如迁移线程)。
// - 其次尝试从当前处理器本地就绪队列选择线程。
// - 若本地队列为空,执行 NUMA 感知调度,在组内其他处理器中“偷取”线程。
// - 若启用了公平调度(Fair Share),则会尝试从 idle-only 队列中获取线程。
// - 若所有尝试失败,返回 NULL,当前核心保持 idle。
//
// 参数:
// IN PKPRCB Prcb
// 指向当前处理器的处理器控制块(Processor Control Block)
//
// IN BOOLEAN IsIdleSchedule
// 表示本次调度是否由空闲线程触发(如 IdleThread 调度器)
//
// 返回值:
// 成功:指向可运行线程的指针(PKTHREAD)
// 失败:返回 NULL 表示没有线程可运行
//

PKTHREAD
KiSearchForNewThread(
IN PKPRCB Prcb,
IN BOOLEAN IsIdleSchedule
)
{
PKTHREAD Thread = Prcb->NextThread;

//
// [Step 1] 若 PRCB 中已经有预选线程(如抢占迁移设定),直接切换它
//
if (Thread) {
Prcb->NextThread = NULL;
Prcb->CurrentThread = Thread;
Thread->State = Running;
return Thread;
}

//
// [Step 2] 本地调度:尝试从当前处理器本地就绪队列中选择线程
//
Thread = KiSelectReadyThread(0, Prcb);
if (Thread) {
Prcb->CurrentThread = Thread;
Thread->State = Running;

if (IsIdleSchedule) {
// 从空闲状态转为活跃,清除“未使用”状态并更新“活跃核心掩码”
_interlockedbittestandreset(&NotUsedCoreProcessor[Prcb->Group], (UCHAR)Prcb->GroupIndex);
_InterlockedAnd(&UsedCoreProcessor[Prcb->Group], ~Prcb->CoreProcessorSet);
}

return Thread;
}

//
// [Step 3] 若本地无线程且非 IdleSchedule,则更新当前核心为“未使用”
//
if (!IsIdleSchedule) {
_interlockedbittestandset(&NotUsedCoreProcessor[Prcb->Group], (UCHAR)Prcb->GroupIndex);

// 若当前 PRCB 控制的所有核心都处于未使用状态,则将其加入 UsedCoreProcessor
if ((Prcb->CoreProcessorSet & NotUsedCoreProcessor[Prcb->Group]) == Prcb->CoreProcessorSet) {
_InterlockedOr(&UsedCoreProcessor[Prcb->Group], Prcb->CoreProcessorSet);
}
}

//
// [Step 4] 本地调度失败,释放当前 PRCB 锁,准备进入远程调度路径
//
KiReleasePrcbLock(Prcb);

//
// [Step 5] 若启用了 CPU 公平调度机制,尝试从 idle-only 队列中拉取线程
//
if (!PsCpuFairShareEnabled ||
!PsReleaseThreadFromIdleOnlyQueue(Prcb->Number, Prcb->Number)) {

//
// [Step 6] NUMA 感知调度:遍历处理器所在 Group 的所有 NUMA 节点
// 若其中有线程可运行,则窃取该线程
//
if ((Prcb->GroupSetMember & KiAllowedProcessorSetInGroup[Prcb->Group]) != 0) {

PKNODE ParentNode = Prcb->ParentNode;
USHORT NodeNumber = ParentNode->NodeNumber;
ULONG GroupSetMember = Prcb->GroupSetMember;
USHORT NodeIndex = 0;

while (NodeIndex < KeNumberNodes) {
PKNODE Node;

if (NodeIndex == 0) {
// 第一个节点为本地节点
Node = ParentNode;
} else {
// 其余节点来自 NUMA 拓扑图(MiNodeGraph)
USHORT NextNodeId = MiNodeGraph[NodeNumber * KeNumberNodes + NodeIndex];
Node = KeNodeBlock[NextNodeId];

// 只处理同 Group 的节点
if (Node->Affinity.Group != Prcb->Group) {
++NodeIndex;
continue;
}
}

//
// 计算当前节点与当前处理器组之间可调度核心集合
// 差集后再排除未使用核心,得到最终 ScanSet
//
ULONG Mask = GroupSetMember ^ Node->Affinity.Mask;
ULONG ScanSet = ~NotUsedCoreProcessor[Node->Affinity.Group] & Mask;

while (ScanSet != 0) {
ULONG Bit;
_BitScanReverse(&Bit, ScanSet);
ScanSet ^= KiMask32Array[Bit];

ULONG ProcIndex = KiProcessorNumberToIndexMappingTable[(Node->Affinity.Group << 6) + Bit];
PKPRCB TargetPrcb = KiProcessorBlock[ProcIndex];

if (TargetPrcb->ReadySummary == 0)
continue;

//
// 处理器间加锁顺序必须遵循地址顺序,避免死锁
//
PKPRCB FirstLock = (Prcb < TargetPrcb) ? Prcb : TargetPrcb;
PKPRCB SecondLock = (Prcb < TargetPrcb) ? TargetPrcb : Prcb;

KiAcquirePrcbLock(FirstLock);
if (FirstLock != SecondLock)
KiAcquirePrcbLock(SecondLock);

//
// 再次检查本地 NextThread 是否可用
//
Thread = Prcb->NextThread;
if (Thread) {
Prcb->NextThread = NULL;

if (Thread != Prcb->IdleThread) {
Thread->State = Running;
Prcb->CurrentThread = Thread;

KiReleasePrcbLock(TargetPrcb);
return Thread;
}

// 如果只是空闲线程,清除 IdleSchedule 并继续
Prcb->IdleSchedule = 0;
KiReleasePrcbLock(Prcb);
Thread = NULL;
} else {
//
// 尝试从目标 PRCB 的 ReadySummary 中找线程
//
Thread = KiFindReadyThread(TargetPrcb->ReadySummary, Prcb, TargetPrcb);
if (Thread) {
Thread->State = Running;
Prcb->CurrentThread = Thread;

// 由于本处理器将获得线程,清除未使用状态
_interlockedbittestandreset(&NotUsedCoreProcessor[Prcb->Group], (UCHAR)Prcb->GroupIndex);
_InterlockedAnd(&UsedCoreProcessor[Prcb->Group], ~Prcb->CoreProcessorSet);

KiReleasePrcbLock(TargetPrcb);
return Thread;
}
}

// 无结果,解锁后继续尝试其他处理器
KiReleasePrcbLock(Prcb);
KiReleasePrcbLock(TargetPrcb);

//
// 再次尝试公平调度线程窃取
//
if (PsCpuFairShareEnabled &&
PsReleaseThreadFromIdleOnlyQueue(Prcb->Number, TargetPrcb->Number)) {
return NULL;
}
}

++NodeIndex;
}
}
}

//
// [Step 7] 所有调度路径失败,返回 NULL,当前处理器保持 idle
//
return NULL;
}

KiSelectReadyThread 是从当前 PRCB 的本地 ReadyList 中选择优先级最高的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//
// 函数名称:
// KiSelectReadyThread
//
// 功能描述:
// 在指定的 PRCB(处理器控制块)中,从不低于指定优先级 (LowPriority)
// 的就绪线程中选择一个可运行线程。该函数用于从每个优先级的就绪线程队列中,
// 快速定位最高优先级的线程并返回其线程对象指针(KTHREAD)。
//
// 参数:
// LowPriority - 线程调度优先级的下限,仅考虑大于等于该优先级的线程。
// Prcb - 指向当前处理器的 PRCB 结构体,包含就绪线程位图和就绪队列。
//
// 返回值:
// 若成功找到一个线程,则返回该线程对象 (PKTHREAD);
// 若没有线程可运行,则返回 NULL。
//
PKTHREAD
KiSelectReadyThread(
IN ULONG LowPriority,
IN PKPRCB Prcb
)
{
ULONG PrioritySet;
ULONG HighPriority;
PLIST_ENTRY ListEntry;
PKTHREAD Thread = NULL;

//
// 就绪线程概览位图(ReadySummary)右移以忽略低于 LowPriority 的部分,
// 从而仅检查感兴趣的优先级范围。
//
PrioritySet = Prcb->ReadySummary >> LowPriority;

if (PrioritySet != 0) {

//
// 使用 BitScanReverse 找到最高就绪优先级(即最左边的 1)
// 这是位图中优先级的偏移值,加上 LowPriority 得到全局优先级。
//
_BitScanReverse(&HighPriority, PrioritySet);
HighPriority += LowPriority;

//
// 获取对应优先级链表的第一个线程。
// DispatcherReadyListHead 每个优先级维护一个双向链表。
//
ListEntry = Prcb->DispatcherReadyListHead[HighPriority].Flink;

//
// 根据 WaitListEntry 字段位置,从链表节点还原出线程对象指针。
//
Thread = CONTAINING_RECORD(ListEntry, KTHREAD, WaitListEntry);

//
// 从链表中移除该线程。如果链表移除后变为空,则清除该优先级对应位。
//
if (RemoveEntryList(&Thread->WaitListEntry)) {
//
// 使用 KiMask32Array 对应位掩码进行位图更新,表示该优先级已无就绪线程。
//
Prcb->ReadySummary ^= KiMask32Array[HighPriority];
}
}

return Thread;
}

KiFindReadyThread 函数主要是在与当前 PRCB 属于同一个 Group 的 NUMA 节点的就绪队列选择线程,在获取时会按照优先级从高到低依次从调度队列中获取。由于在获取时还要线程是否允许运行在当前处理器上,因此需要遍历链表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//
// KiFindReadyThread
//
// 函数作用:
// 在目标处理器的 ReadySummary 中查找一个可以运行在当前处理器上的线程,
// 按优先级从高到低依次查找,就绪线程列表存放在 DispatcherReadyListHead 中。
// 若找到符合亲和性(Affinity)要求的线程,则将其从队列中移除并返回。
//
// 参数:
// IN ULONG PrioritySet
// 目标处理器的 ReadySummary 位图(每一位对应一个优先级)
//
// IN PKPRCB CurrentPrcb
// 当前尝试调度线程的处理器控制块(用于检查亲和性)
//
// IN PKPRCB TargetPrcb
// 目标处理器控制块(其就绪队列中查找线程)
//
// 返回值:
// 成功:返回符合亲和性的就绪线程指针(PKTHREAD)
// 失败:返回 NULL 表示无合适线程
//

PKTHREAD
FASTCALL
KiFindReadyThread (
IN ULONG PrioritySet,
IN PKPRCB CurrentPrcb,
IN PKPRCB TargetPrcb
)
{
ULONG HighPriority;
ULONG Index;
PLIST_ENTRY ListHead;
PLIST_ENTRY Flink;
PKTHREAD Thread;

//
// 遍历 PrioritySet 中的所有优先级位(从高到低)
//
while (TRUE) {

//
// 使用位扫描查找最高优先级(即 PrioritySet 中最高位为 1 的位置)
//
if (!_BitScanReverse(&HighPriority, PrioritySet)) {
break; // 无可用优先级,跳出主循环
}

// 清除当前优先级位(表示该优先级已尝试)
PrioritySet ^= KiMask32Array[HighPriority];
Index = HighPriority;

//
// 获取该优先级对应的调度队列头(双向链表)
//
ListHead = &TargetPrcb->DispatcherReadyListHead[HighPriority];
Flink = ListHead->Flink;

//
// 遍历该优先级上的线程列表
//
while (Flink != ListHead) {

//
// 从 WaitListEntry 得到所属线程结构体 KTHREAD
//
Thread = CONTAINING_RECORD(Flink, KTHREAD, WaitListEntry);

//
// 检查线程是否允许运行在当前处理器上:
// - 同一 Group;
// - 当前处理器在其 Affinity 掩码中
//
if ((Thread->Affinity.Group == CurrentPrcb->Group) &&
((Thread->Affinity.Mask & CurrentPrcb->GroupSetMember) != 0)) {

//
// 从调度队列中移除此线程
// 若移除后队列为空,则清除该优先级的 ReadySummary 位
//
if (RemoveEntryList(Flink)) {
TargetPrcb->ReadySummary ^= KiMask32Array[Index];
}

//
// 设置该线程的下一目标处理器为当前 PRCB
//
Thread->NextProcessor = (UCHAR)CurrentPrcb->Number;
return Thread;
}

// 遍历下一个线程
Flink = Flink->Flink;
}

//
// 若当前优先级链表未找到合适线程,继续尝试下一个优先级
//
if (PrioritySet == 0) {
break;
}
}

//
// 所有优先级队列均未找到符合亲和性的线程,返回 NULL
//
return NULL;
}

抢占式线程切换

以 Windows 在时间片到期后调度线程为例,调用栈如下:

1
2
3
4
5
6
7
8
9
10
nt!KiQuantumEnd                      // 【线程时间片到期】调度器核心函数:检查当前线程量子是否用尽,若需调度则准备切换线程
nt!KiDispatchInterrupt // 【内核中断分发器】统一入口,检查并触发调度/APC 等软中断
hal!HalpDispatchSoftwareInterrupt // 【分发软中断】处理 DPC/APC/调度等软中断逻辑
hal!HalpCheckForSoftwareInterrupt // 【检查是否存在待处理的软中断】决定是否继续调度分发
hal!KfLowerIrql // 【降低 IRQL】从中断级别降至 DPC_LEVEL,允许软中断处理
hal!HalRequestSoftwareInterrupt // 【请求软中断】在时钟中断中发起调度请求(如 DPC)
KeUpdateRunTime // 【更新当前线程运行时间】维护线程的 TSC/CycleTime,用于调度决策
KeUpdateSystemTime // 【更新系统时间】更新 TickCount、系统时间并判断是否处理线程量子
nt!KeUpdateSystemTimeAssist // 【辅助系统时间更新】KeUpdateSystemTime 的子过程,处理 Tick 与定时器逻辑
hal!HalpHpetClockInterrupt // 【HPET 硬件定时器中断】周期性触发,驱动线程调度与系统时钟更新

其中 KiQuantumEnd 会在时间片用尽后进行线程切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//
// KiQuantumEnd
//
// 描述:
// 调度器在当前线程时间片用尽时调用该函数,用于判断是否需要发生线程切换,
// 并完成当前线程的调度后处理,包括:
// - 时间片到期时的优先级调整
// - NextThread 的选择与迁移逻辑
// - 执行线程上下文切换(若满足条件)
// - 周期统计与性能计数更新
//
// 典型触发:
// - DPC 层检查到当前线程已运行到 QuantumTarget
// - 当前处理器调度循环主动调用
//
VOID KiQuantumEnd()
{
PKPRCB Prcb = KeGetPcr()->Prcb;
PETHREAD CurrentThread = (PETHREAD)Prcb->CurrentThread;
PKTHREAD Thread = &CurrentThread->Tcb;
PKPROCESS Process = Thread->ApcState.Process;

// ------------------------------------------------------------
// 同步读取 Thread->CycleTime 和 Thread->HighCycleTime
// 防止在读取 64 位 CycleTime 的过程中,HighPart 被更新造成不一致。
// 该机制用于支持 TSC-based profiling,确保周期读数精确。
// ------------------------------------------------------------
while (TRUE) {
if (Thread->CycleTime.HighPart == Thread->HighCycleTime)
break;
_mm_pause(); // 避免总线占用,提示处理器进入 pause 等待状态
}

// ------------------------------------------------------------
// 加锁线程和 PRCB,确保当前调度状态被独占修改。
// 调度器所有线程切换逻辑都要持有这两把锁,避免竞态。
// ------------------------------------------------------------
KiAcquireThreadLock(Thread);
KiAcquirePrcbLock(Prcb);

// ------------------------------------------------------------
// 判断当前线程是否已用完时间片(CycleTime >= QuantumTarget)
// 若是,则可能进行优先级调整、迁移、调度新线程等操作
// ------------------------------------------------------------
if (Thread->CycleTime.QuadPart >= Thread->QuantumTarget) {
UCHAR Quantum = 0;

// 若进程禁用了量子管理(DisableQuantum)且线程是实时优先级
// 则分配一个极大时间片(MAXCHAR)以延迟下次切换
if (Process->DisableQuantum && Thread->Priority >= LOW_REALTIME_PRIORITY) {
Quantum = MAXCHAR;
} else {
Quantum = Thread->QuantumReset;

// 若非实时线程,进行优先级衰减(ForegroundBoost + UnusualBoost)
if (Thread->Priority < LOW_REALTIME_PRIORITY) {
UCHAR Decrement = Thread->UnusualBoost + Thread->ForegroundBoost;
KPRIORITY NewPriority = Thread->Priority - Decrement;

// 最低不能低于 BasePriority,防止优先级跌穿
if (NewPriority < Thread->BasePriority)
NewPriority = Thread->BasePriority;

Thread->Priority = NewPriority;
Thread->PriorityDecrement = 0; // 清除本次衰减
}

// 如果没有预设 NextThread,就从就绪队列中抢一个优先线程执行
if (!Prcb->NextThread) {
KPRIORITY Priority = Thread->Priority;
ULONG Summary = Prcb->ReadySummary >> Priority;
PKTHREAD Next = NULL;

if (Summary) {
ULONG Offset;
_BitScanReverse(&Offset, Summary);
ULONG Index = Priority + Offset;

// 抢占就绪链表头部线程
PLIST_ENTRY Entry = Prcb->DispatcherReadyListHead[Index].Flink;
Next = CONTAINING_RECORD(Entry, KTHREAD, WaitListEntry);

// 从链表中摘除 Entry
if (RemoveEntryList(Entry))
Prcb->ReadySummary ^= KiMask32Array[Index];
}

Prcb->NextThread = Next;
} else {
Thread->Preempted = FALSE; // 没有被其他线程抢占
}
}

// 设置下一次线程时间片截止时间(以 TSC 单位表示)
Thread->QuantumTarget = Thread->CycleTime.QuadPart + (Quantum * KiCyclesPerClockQuantum);

// ------------------------------------------------------------
// 检查是否可以尝试进行 CPU 核迁移(CoreProcessorSet ≠ GroupSetMember)
// 用于 NUMA/多核系统中资源负载均衡,减少线程驻留延迟
// ------------------------------------------------------------
if (Prcb->GroupSetMember != Prcb->CoreProcessorSet &&
Thread->QuantumEndMigrate) // 如果允许量子结束迁移
{
Thread->QuantumEndMigrate = FALSE;

// 条件:1. 当前没有下一线程,2. 存在可用空闲核
// 且 3. 理想核可用并允许迁移
if (!Prcb->NextThread &&
(Prcb->CoreProcessorSet &
(Prcb->GroupSetMember | NotUsedCoreProcessor[Prcb->Group])) != Prcb->CoreProcessorSet &&
(Thread->Affinity.Mask &
KiProcessorBlock[Thread->IdealProcessor]->ParentNode->Affinity.Mask &
UsedCoreProcessor[Thread->Affinity.Group])) {

KiSelectNextThread(Prcb); // 从远核选择可调度线程
Thread->ForceDeferSchedule = TRUE; // 强制下次调度
Thread->QuantumEndMigrate = TRUE; // 设置已迁移标志
}
}
}

// ------------------------------------------------------------
// 已完成本线程调度状态更新,释放线程锁
// ------------------------------------------------------------
KiReleaseThreadLock(Thread);

// ------------------------------------------------------------
// 若调度器已选出新线程,则进行上下文切换
// ------------------------------------------------------------
PKTHREAD NextThread = Prcb->NextThread;

if (NextThread) {
Prcb->NextThread = NULL;
_disable(); // 防止切换期间中断

PKTHREAD OldThread = Prcb->CurrentThread;
Prcb->NestingLevel = 1;

ULONGLONG Now = __rdtsc(); // 当前时间戳计数器
ULONGLONG Delta = Now - Prcb->StartCycles;

// 累加当前线程运行的 TSC 周期
OldThread->CycleTime.QuadPart += Delta;
OldThread->HighCycleTime = OldThread->CycleTime.HighPart;
Prcb->StartCycles = Now;

// 若启用了 CycleProfiling,则对进程累加运行周期
if (OldThread->Header.CycleProfiling)
PsChargeProcessCpuCycles(Prcb, Delta, Delta >> 32);

// 若启用了 CounterProfiling,则停止性能计数累加
if (OldThread->Header.CounterProfiling)
KiEndCounterAccumulation(OldThread);

_enable(); // 恢复中断

// ------------------------------------------------------------
// 更新调度器当前线程,正式切换
// ------------------------------------------------------------
Prcb->CurrentThread = NextThread;
NextThread->State = Running;

Thread->WaitReason = WrQuantumEnd; // 设置当前线程等待原因为时间片结束
KiQueueReadyThread(Thread, Prcb); // 放回就绪队列
Thread->WaitIrql = APC_LEVEL; // 当前线程返回后 IRQL 为 APC

KiSwapContext(Thread, NextThread); // 执行上下文切换
}

KiReleasePrcbLock(Prcb); // 释放 PRCB 锁并返回
}

其中选择下一个可用现成的 KiSelectNextThread 函数本质上还是调用了 KiSelectReadyThread 函数,与主动切换类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//
// KiSelectNextThread
//
// 描述:
// 在当前处理器的就绪线程队列中选择下一个可运行的线程。
// 若没有可运行线程,则选择 IdleThread,并更新空闲核心状态与调度标志。
// 最终将选中线程设置为 Prcb->NextThread,并返回之。
//
PKTHREAD
KiSelectNextThread(
IN PKPRCB Prcb
)
{
PKTHREAD Thread;

// ------------------------------------------------------------
// [Step 1] 从本地 ReadyList 中选择最高优先级的线程
// ------------------------------------------------------------
Thread = KiSelectReadyThread(0, Prcb);
if (!Thread)
{
// ------------------------------------------------------------
// [Step 2] 若无就绪线程,选择 IdleThread 并标记为 IdleSchedule
// ------------------------------------------------------------
Thread = Prcb->IdleThread;
Prcb->IdleSchedule = 1;

// 标记该核心为 NotUsed
_interlockedbittestandset(
&NotUsedCoreProcessor[Prcb->Group],
(UCHAR)Prcb->GroupIndex
);

// ------------------------------------------------------------
// [Step 3] 若该 PRCB 管辖的所有核心都未使用,则标记 UsedCoreProcessor
// ------------------------------------------------------------
if ((Prcb->CoreProcessorSet & NotUsedCoreProcessor[Prcb->Group]) == Prcb->CoreProcessorSet)
{
_InterlockedOr(
&UsedCoreProcessor[Prcb->Group],
Prcb->CoreProcessorSet
);
}
}

// ------------------------------------------------------------
// [Step 4] 设置状态为 Standby 并保存为 NextThread
// ------------------------------------------------------------
Thread->State = Standby;
Prcb->NextThread = Thread;

return Thread;
}
  • Title: windows 进程线程
  • Author: sky123
  • Created at : 2022-09-28 11:45:14
  • Updated at : 2025-07-01 10:00:04
  • Link: https://skyi23.github.io/2022/09/28/windows 进程线程/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments